MongoDB: incorrect update count - javascript

In my database, I have a field called 'fruits' which is a simple array. When inserting elements in this array, I use $addToSet to only insert elements that do not exist already in this array.
I want to know whether or not the element I insert really modified this field. However, the docModified parameter in the callback always returns 1 even when I try to add an element that already exists.
model.update (
{ username: username }, // find the document
{ $addToSet : { fruits: "apple" } }, // add "apple" to fruits field
function (err, docModified) {
console.log(docModified);
// !PROBLEM: this will print "1" no matter what
}
);
Does anyone know why? Thanks a lot! (btw I'm using Mongoose)

The current method implementations in mongoose use the legacy write concern API to determine the count of modified documents. As you note, even if there is no actual change to the content such as an $addToSet operation that does not add a new member to the set, the modified count will be returned.
As long as your MongoDB server version is recent enough ( needs to be MongoDB 2.6 or greater ) and your mongoose version is recent enough an bundles a recent mongodb driver, then you can get the correct numbers from the Bulk Operations API responses.
To do this with mongoose you call the .collection accessor on the model, which returns a native driver collection object:
var bulk = Model.collection.initializeOrderedBulkOp();
bulk.find({ username: username })
.updateOne({ $addToSet : { fruits: "apple" } });
bulk.execute(function(err,result) {
if (err) throw err;
console.log( JSON.stringify( result, undefined, 4 ) );
})
The "result" that is returned is an object conforming to the BulkWriteResult() specification, which more or less will look like this:
{
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 3,
"nModified" : 3,
"nRemoved" : 1,
"upserted" : [ ]
}
But specifically, where you use $addToSet and the member already exists, the response will contain "nMatched": 1 and "nModified": 0, which is the result you want to see, confirming that nothing was in fact added to the set.
In the MongoDB 2.6 shell, all of the update and insert methods try to use this API where it is available and only fallback to the legacy implementation when connecting to an older server. So If you did this in a modern shell with a modern server you would see the same results.
Hopefully mongoose itself will be altered to also use these methods where available and provide access to the same response. It seems to be the way of the future, but the codebase is yet to catch up and utilize this.
Note: The one thing to be careful of when using any of the native driver methods after accessing the collection object is to make sure that mongoose has an active connection to the database at the time this is called. Mongoose hides this by queuing up any request until a connection is actually established.
So if you are going straight to the collection object methods, then you might want to make sure you are waiting for the "open" event on the connection somewhere before that code executes.

"number affected is the number of docs updated, even if the new values are identical. this comes straight from mongo." I got this from this forum post: https://github.com/LearnBoost/mongoose/issues/867
This means you'll have to come up with a different way to determine if the element was missing from the array before you update. I would recommend pull all the documents and iterating through them before the update. Obviously it's not ideal, but i don't think there's any other way to do it.
Hope this helps.

Related

Sequelize .update() not using the correct where and updates all rows

I'm trying to create an approval command.
Everything about it, should be working, but it never works correctly.
I've changed my Database to have "id" first, but it still prefers to update on "user" instead.
I've tried having my code just be; but that never worked out. Even though I use it everywhere else.
await ticketApproval.update({status: 1});
I've checked documentation and other StackOverflow questions and answers.
Nothing has worked yet, but from what I can tell, nothing in my code should be telling it to update on the "user" id. Since I only ever tell it to search on the ticket "id" and "server" id
It feels like it's just outright ignoring the Where.
Code
Console
Test Sample
You clearly confuse the update method of a record instance with the static update of a model.
Only static update has the where option because it updates several records in DB using passed conditions in contrast to the instance update method that uses a primary key value of a certain record to update it with passed values.
Compare:
// here we have a model instance for a concrete record
const ticketApproval = await Ticket.findOne({
where: {
id: ticketToApprove
}
})
// by calling the instance `update` we always update this concrete record only
// using a primary key value condition, see a primary key field defined in a model
await ticketApproval.update({ status: 1 });
with
// by calling the static `update` we update all records that satisfy the indicated conditions
await Ticket.update({ status: 1 }, {
where: {
id: ticketToApprove
}
});

Created a new object, then put an array after it in a loop to patch a property, it works but I dont know why?

I'm working with JavaScript in Node.js and using Express with some mongoose and following a well known RESTful API tutorial on youtube, I've come to patching the API and have been trying to understand why the following code works for sometime now;
updateItem = {};
for (const changes of req.body) {
updateItem[changes.propName] = changes.value;
}
Product.updateOne({ _id: id }, { $set: updateItem })
The rest is just your standard .then().catch() to send the response status, but I'm lost on how creating the object then placing it before an array works to update a value.
It's my current understanding that the object must be instantiated before use, I couldn't just put brackets there and have it work, even if I wasn't using it to set something later. Then I loop through the changes from the request body which must be an array to allow looping, but here's where I get lost.
Does the array of iterated prop names changes.propName get placed inside the updateItem object which is then set to the changed values from the array of properties that are being changed? Do I need to understand $set syntax more? I'm struggling to pick it apart to make it longer or simpler but better to understand.
This is the json array setup I'm passing for testing through postman if it helps;
[
{
"propName": "name", "value": "placeholder user"
}
]
I was unable to find anything to help me understand the interactions going on here, I haven't seen something like this before either but please redirect me if this has already been asked.

mongoose - how do I get the elements removed from $pull

I'm using $pull to pull a subdocument within an array of a document.
Don't know if it matters but in my case the subdocuments contain _id so they are indexed.
Here are JSONs that describes the schemas:
user: {
_id: String,
items: [UserItem]
}
UserItem: {
_id: String,
score: Number
}
Now my problem is this: I am using $pull to remove certain UserItem's from a User.
var delta = {$pull:{}};
delta.$pull.items={};
delta.$pull.items._id = {$in: ["mongoID1", "mongoID2" ...]};
User.findOneAndUpdate(query, delta, {multi: true}, function(err, data){
//whatever...
});
What i get in data here is the User object after the change, when what i wish to get is the items that were removed from the array (satellite data).
Can this be done with one call to the mongo or do I have to do 2 calls: 1 find and 1 $pull?
Thanks for the help.
You really cannot do this, or at least there is nothing that is going to return the "actual" elements that were "pulled" from the array in any response, even with the newer WriteResponse objects available to the newer Bulk Operations API ( which is kind of the way forward ).
The only way you can really do this is by "knowing" the elements you are "intending" to "pull", and then comparing that to the "original" state of the document before it was modified. The basic MongoDB .findAndModify() method allows this, as do the mongoose wrappers of .findByIdAndUpdate() as well and .findOneAndUpdate().
Basic usage premise:
var removing = [ "MongoId1", "MongoId2" ];
Model.findOneAndUpdate(
query,
{ "$pull": { "items._id": { "$in": removing } } },
{ "new": false },
function(err,doc) {
var removed = doc.items.filter(function(item) {
return removing.indexOf(item) != -1;
});
if ( removed.length > 0 )
console.log(removed);
}
);
Or something along those lines. You basically "turn around" the default mongoose .findOneAndUpdate() ( same for the other methods ) behavior and ask for the "original" document before it was modified. If the elements you asked to "pull" were present in the array then you report them, or inspect / return true or whatever.
So the mongoose methods differ from the reference implementation by returning the "new" document by default. Turn this off, and then you can "compare".
Further notes: "multi" is not a valid option here. The method modifies "one" document by definition. Also you make a statement that the array sub-documents conatain an _id. This is done by "mongoose" by default. But those _id values in the array are "not indexed" unless you specifically define an index on that field. The only default index is the "primary document" _id.

Saving a modified document (with a modified subdocument array) in Mongoose model

The code I currently have is:
User.findOne(
{
"subUsers.email" : userEmail
},
{
subUsers : {
$elemMatch: {
email : userEmail }
}
},
function(err, user){
if(user){
var information = user.subUsers[0].information.id(id);
information.arrayA.push({someId : "something"});
user.save(callback(err)); // Also tried information.save()
// without luck
}
callback(err);
}
);
This doesn't return any kind of error, but when I check the DB, the new array element hasn't been pushed (the whole document is intact).
Any help would be much appreciated. Thanks!
You should probably check out the first faq, here: http://mongoosejs.com/docs/faq.html
Mongoose doesn't create getters/setters for array indexes; without them mongoose never gets
notified of the change and so doesn't know to persist the new value. The work-around is to
use [MongooseArray set][1] available in Mongoose >= 3.2.0.
So in your case, you want to add this third line
var information = user.subUsers[0].information.id(id);
information.arrayA.push({someId : "something"});
user.subUsers.set(0, information);
Or something like that.
As of today, Mongoose is currently not prepared for multilevel nesting in an atomic way.
Therefore, even if it's going back to a kind-of relational database, in this case it's better to split the nesting into at least 2 collections, and reference using the automatically generated ObjectId.

BackboneJS collection.reset() vs collection.fetch()

I have read and read the docs on these two methods, but for the life of me cannot work out why you might use one over the other?
Could someone just give me a basic code situation where one would be application and the other wouldn't.
reset sets the collection with an array of models that you specify:
collection.reset( [ { name: "model1" }, { name: "model2" } ] );
fetch retrieves the collection data from the server, using the URL you've specified for the collection.
collection.fetch( { url: someUrl, success: function(collection) {
// collection has values from someUrl
} } );
Here's a Fiddle illustrating the difference.
We're assuming here that you've read the documentation, else it'l be a little confusing here.
If you look at documentation of fetch and reset, what it says is, suppose you have specified the url property of the collection - which might be pointing to some server code, and should return a json array of models, and you want the collection to be filled with the models being returned, you will use fetch.
For example you have the following json being returned from the server on the collection url:
[{
id : 1,
name : "a"
}, {
id : 2,
name : "b"
}, {
id : 3,
name : "c"
}]
Which will create 3 models in your collection after successful fetch. If you hunt for the code of collection fetch here you will see that fetch will get the response and internally will call either reset or add based on options specified.
So, coming back to discussion, reset assumes that we already have json of models, which we want to be stored in collection, we will pass it as a parameter to it. In your life, ever if you want to update the collection and you already have the models on client side, then you don't need to use fetch, reset will do your job.
Hence, if you want to the same json to be filled in the collection with the help of reset you can do something like this:
var _self = this;
$.getJSON("url", function(response) {
_self.reset(response); // assuming response returns the same json as above
});
Well, this is not a practice to be followed, for this scenario fetch is better, its just used for example.
Another example of reset is on the documentation page.
Hope it gives a little bit of idea and makes your life better :)
reset() is used for replacing collection with new array. For example:
#collection.reset(#full_collection.models)
would load #full_collections models, however
#collection.reset()
would return empty collection.
And fetch() function returns default collection of model

Categories