I'm creating an application in Node that has some CRUD components. On one of my data objects, I have a save() method that is meant to update a record if the object has an id that is found in the collection, or upsert a new document if not. Additionally, if doing an upsert I'd like to get back the _id for the document generated by Mongo.
It seems that findAndModify would do this, and, indeed, it does return an _id value from the database. In my query clause, I am filtering by _id. If my data object doesn't have an id, Mongo correctly does an upsert, however, no matter what _id value it returns, in addition to the the keys I am setting in the update clause, it also sets the _id on the document based on what value I used in the query clause. Some code for clarity:
User.prototype.save = function(callback) {
var that = this;
var args = {
'query' : { _id: this.getId() }, //getId() returns empty string if not set
'update' : { $set : {
firstName : this.firstName,
lastName : this.lastName,
email : this.email
//_id : this.getId()
// which is blank, is magically getting added due to query clause
}},
'new' : true,
'upsert' : true,
'fields' : {'_id' : true}
};
this.db.collection(dbName).findAndModify(args, function(err, doc){
if(!that.getId()) {
that.setId(doc._id);
}
if (typeof(callback) === "function"){
callback.call(that);
}
});
}
I'm simply looking for the semantics of update that also happens to return a Mongo-generated _id on upsert. I do not want the values of the query clause to additionally be appended as if they were in the update map. Is there any way to achieve what I am getting at?
You can generate the _id client side, with new new require('mongodb').ObjectID()
Then you can just do a regular upsert (no need to do findandmodify) because you already have the _id.
However, if you are using findAndModify, keep in mind that the node driver accepts the arguments to this function positionally, not as an object (like in the regular mongo shell).
The correct format to do findandmodify with the node driver looks like this:
collection.findAndModify(criteria, sort, update[, options, callback])
(options and callback are optional params). Full docs here:
https://github.com/mongodb/node-mongodb-native/blob/master/docs/insert.md
Related
I need to pass to the updateMany method in a Node/Typescript software a piece of query retrieved from the database:
{'$unset': {'extras' : {'origin': ''}}}
In the db, the above query is stored as a field of an Object:
"cleanup.aggregated_records_to_modify" : {
"update_fields_by" : "{'$unset': {'extras' : {'origin': ''}}}"
}
If I pass the update_fields_by to the updateMany mondodb Nodejs driver method, I have an error saying ""MongoError: the update operation document must contain atomic operators." (it receives a string instead of an object?!?); if, instead, I create an object variable:
const queryTemp = { $unset: { extras: { origin: "" } } };
to give to the updateMany, all goes well.
How can I retrieve the field from db and correctly pass it to the update method as an object?
If you use JSON.parse(foo) on your variable, to convert it from string to an object
There was a problem (a bug?) with the tool I use to manage MondoDB; I cannot store an object with a key starting with $ because I receive the error "Illegal argument: Invalid BSON field name $unset". I have add the $ symbol programmatically.
I was trying to console.log(record._id) all of records on my mongodb collection using Mongoose. I kept getting undefined for each of the _id values.
I struggled until I bumped into this post. Then I used console.dir to find the location of the _id and used that in my console.log:
MySchemaModel.find({}).then(function(records) {
records.forEach(function(record) {
console.log(record._doc._id); // <-- I added ._doc
});
});
But, this looks down-right hacky. Is there a better way to do this?
NOTE: This isn't just something that affects console.log. I'm just keeping the question narrow.
If you want to customize/edit record then you should use .lean() function.The .lean() function will turn it into a normal JavaScript object. If you don't use .lean() function then each record is still a mongoose document and _id behaves differently in that context. So can use like
MySchemaModel.find({}).lean().exec(function(error, records) {
records.forEach(function(record) {
console.log(record._id);
});
});
N.B: when use .exec() then first parameter used for error and second one for success data.
Mongoose assigns each of your schemas an id virtual getter by default
which returns the documents _id field cast to a string, or in the case
of ObjectIds, its hexString. If you don't want an id getter added to
your schema, you may disable it passing this option at schema
construction time.
Source: Mongoose Docs
var schema = new Schema({ name: String }, { id: false });
var Page = mongoose.model('Page', schema);
var p = new Page({ name: 'mongodb.org' });
console.log(p.id); // '50341373e894ad16347efe01'
I guess the issue is with .then promise, I have never seen that.
MySchemaModel.find({}).then
So just try simple .exec call with callback.
MySchemaModel.find({}).exec(function(records) {
records.forEach(function(record) {
console.log(record._id);
});
});
The problem is that each record is still a mongoose document and _id behaves differently in that context. The .lean() function will turn it into a normal JavaScript object.
MySchemaModel.find({}).lean().then(function(records) {
records.forEach(function(record) {
console.log(record._id);
});
});
you can also use the .map() method :
MySchemaModel.find({}).exec(function(records) {
console.log(records.map(record => record._id);
});
if you are using a model you don't get the full object but an instance of _doc as record
so you should directly
console.log(record._id)
or
console.log(record._id.valueOf())
but when you return record as response you get the full object so it's better to use .find().lean()
So I'm aware how I should add any regular string/object to a JSON object - however, I need to add the following Mongo command to my JSON object:
$set : { "author" : req.body.name }
As you can see, it's not as simple as just doing:
myJsonObject.author = "$set..." etc.
Sorry if this is a dupe question - this isn't the easiest subject to Google without tons of unrelated answers popping up.
The reason I need to know this because I want to be able to build a JSON object based on what the user has changed in the form. So a new problem I have encountered is that even if I can build the JSON object with multiple items to change, MongoDB only updates the last item in the object. My example JSON object is here:
updateItem = {
$set : { "name" : "Squid bonobo" },
$set : { "author" : "Mardy Bum" }
};
and the snippet of code which issues the update command is as follows:
updateData.update(searchItem, updateItem, false, true, function(err, results) {
console.log(results);
db.close();
});
I found this solution at the following git site.
Can anybody help with either of these questions?
Thanks for your help :)
Cameron
You need to form your JSON in the below structure:
updateItem = {
$set : { "name":"Squid bonobo","author":"Mardy Bum"}
}
Javascript Objects can be declared with duplicate keys/properties, but their values get over-written, with the latest bound value for the key, in any order.
Here, $set is a key/property which has been set twice to the same object referenced by the variable updateItem, hence only one value, that is last encountered, will be associated to the key. In this case, the value that takes precedence is:
$set:{ "author":"Mardy Bum" }
and the final query that gets executed becomes,
updateItem = {
$set : { "author" : "Mardy Bum" }
};
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.
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.