I would like to retrieve some data from a Mongoose setting in my Node.js application. I noticed that no matter what I write as field selection, I always get the _id field. Is there a way not to fetch it?
This is how I do right now:
Transaction.find({username : user.username}, ['uniqueId', 'timeout', 'confirmation_link', 'item_name'], function(err, txs){
console.log("user : " + user.username + " with txs: " + txs);
callback(txs);
});
And logs me the results which contain the _id field.
Another way is to use text argument with prefix - which will exclude this or that field from the result:
Entity.find({ ... }, '-_id field1 field2', function(err, entity) {
console.log(entity); // { field1: '...', field2: '...' }
});
_id must be specifically excluded. For example,
Transaction.find({username : user.username}, { '_id': 0, 'uniqueId' :1, 'timeout': 1, 'confirmation_link': 1, 'item_name': 1}, function(err, txs){
console.log("user : " + user.username + " with txs: " + txs);
callback(txs);
});
Another approach:
Augment the .toJSON() of the schema that it deletes the _id and the __v fields
Call .toJSON() on all DB objects sent to client
Extra benefit #1: you can use item.id === 'something' because typeof id === 'string', not ObjectId.
Extra benefit #2: When you got gan object back from the client and you want to search / update then you don't have to manually delete _id because there is none, just an id which is ignored.
Augmenting JSON:
mySchema.set('toJSON', {
virtuals: true,
transform: (doc, ret, options) => {
delete ret.__v;
ret.id = ret._id.toString();
delete ret._id;
},
});
So you can use:
let item = (await MyCollection.findOne({/* search */}).exec()).toJSON();
if (item.id === 'someString') return item;
I know it's ugly. But it's the best bad idea that I have so far.
In 5.2.13 version of Mongoose (Sept 2018)- using the query builder approach the same can be converted to
async function getUserDetails(user) {
try {
if (!user || !user.name) return;
const result = await Transaction.
find({username : user.username}).
select('uniqueId timeout confirmation_link item_name -_id');
// Adding minus sign before the _id (like -_id) in the select string unselects the _id which is sent by default.
console.log(result);
} catch(ex) {
return ex
}
}
The easiest thing you can do is something like this:
Transaction.find({username : user.username}, {_id: 0}, (err, txs) => {
// the return document won't contain _id field
// callback function body
}
Just remember that in the second object passed in the find()-
Pass 0 as the value to the specific key that you wish not to fetch
from the mongodb database.
Pass 1 as the value when you wish to
fetch from the mongodb database.
Related
I currently have an upsert function in my project which works but my main problem is that it creates another instance of the record, and updates the new instance instead. This is the code:
router.route('/carousel/update/:_id').put(function(req, res) {
var id;
if(req.params._id == 'undefined'){
id = crypto.randomBytes(12).toString('hex');
}
else {
id = ObjectId(req.params._id)
}
db.collection('home').updateOne({"_id": id},
{$set: req.body}, {upsert: true}, (err, results) => {
if (err) throw err;
res.send(results)
console.log(req.body)
});
});
The problem:
1. It mystifies me that mongoDB takes my crypto generated _id and takes it as the new _id for the upserted document. Why is that? When {upsert: true}, isn't mongoDB supposed to generate a new _id?
2. Because of the nature of problem 1, whenever I try to update the original document, it updates the upserted document instead since they have the same _id values even though their _ids are positioned at different document levels.
In conclusion, when given a 'home' document, how do I upsert correctly without adding a new record with the same values and _ids?
Thanks for your help!
EDIT
This is the JSON body content of the document with custom generated _id using crypto:
{
"_id": "1262d480eea83567181b3206",
"header": "hello",
"subheader": "hello"
}
Whereas, this is the body content of the upserted document.
{
"_id": {
"$oid": "1262d480eea83567181b3206"
},
"header": "helloasad",
"subheader": "helloasda"
}
As observed, after upserting, it takes the same _id value of the original document but on another document level.
A possible solution/explanation based on #Ashwanth Madhav information:
In your code 'id' was being sent to the update as a String type, but the id in MongoDB is an ObjectId type:
Code will be something like that:
var id;
if(req.params._id == 'undefined'){
// 'id' NEED TO BE AN ObjectId...
// 'id' WAS BEING SENT AS A 'String'
id = ObjectId(crypto.randomBytes(12).toString('hex'));
}
else {
id = ObjectId(req.params._id)
}
I'm sending the following object for mongoose to update:
{
"name" : "John",
"fruit" : "5a3d678d9b1549979d81c6ba" // this is an objectID
}
and the method:
db_user.findByIdAndUpdate(req.params.id, req.body).then(function(user, err) {
res.send(user);
});
This works fine if I replace the fruit with another fruitID which is what I'm trying to update.
However, if I send an object with no fruit, it won't remove the fruit field from the user document. Such as:
{
"name" : "John"
}
It will update whatever name I pass, but it won't remove the fruit field. Why?
Also: I can't be checking for each field that I'm sending or not and do an $unset for each accordingly, because I'm sending a lot of fields. I just want Mongoose to override the old document with the new one.
I know this is a little complicated / ugly and I'm sure there is an easier way but this should work if you want to update on specific thing and not have it fill the other values in blank.
function updateItem(req, res) {
function GetObjectFromKeyValuePairs(pairs) {
var tmp = {};
for(var key in pairs)
if(key[0] !== "_")
if(pairs[key].length !== 0)
tmp[`${key}`] = `${pairs[key]}`;
return tmp;
}
let updateOnlyChangedVals = GetObjectFromKeyValuePairs(req.body);
DB.Item.update({_id: req.params.id}, {$set: updateOnlyChangedVals},{new:true}, (err, uItem) => {
if (err) { return console.log("index error: " + err); }
res.json(uItem);
});
}
I'm trying to determine whether the document was found in my findOneAndUpdate operation. If it wasn't, I return a 404 not found error. I figured I'd use the "passRawValue" option Mongoose provides, and check for a raw value- if raw is undefined, I know the doc was not found.
However regardless whether the doc is found or not, my raw value is undefined. I've verified that the doc I'm trying to update is in the DB at the time of the query by running a simple "findOne" query just before the update. Where am I going wrong?
let updateItemById = (userId, itemId, params, cb) => {
//this finds and prints the document I'm testing with -- I know its in the DB
// Item.findOne({ "_id" : itemId, ownerId: userId }, (err, doc) => {
// if (doc) {
// console.log("This is the doc: ", doc);
// }
// });
Item.findOneAndUpdate({ "_id" : itemId, ownerId: userId },
{
$set: {
params
}
}, { runValidators: 1, passRawResult: true}, (err, doc, raw) => {
if (err) {
//winston.log
return cb(ErrorTypes.serverError(), false);
}
else if (raw) {
return cb(null, true);
}
else {
return cb(ErrorTypes.notFound(), false);
}
});
}
Hi I have a hunch that you are passing params that has a property that doesn't exist in the document in the database. In such case, nothing was modified, hence db doesn't return raw as the third parameter.
Update:
So I did some few tests of my own, and I see that if we pass option strict:false then your code should work as intended. So your options section will look like this
{ runValidators: 1, passRawResult: true, strict: false, new:true}
Explanation:
Mongoose has a strict option which by default is true. It makes sure that the values being updated is defined in the schema. So when we provide the option strict as false, as described in the [mongoose documentation] (http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate) we can achieve updating document with new field.
I also added new:true option which will return you the updated document.
P.S.
I would like to add though, since our upsert is false, which means it won't insert new document when a match is not found, it will return null for doc, and you can simple check on that. Why are you checking on raw? Is there any particular reason for this?
I know it's been awhile but I had the same problem here so I decided to leave an answer that maybe can help other people.
I was able to check whether the findOneAndUpdate() method found a document or not by checking if the doc parameter was null on the callback function:
async Update(request: Request, response: Response) {
const productId = request.params.id;
const query = { _id: productId };
const options = { new: true };
try {
await Product.findOneAndUpdate(query, request.body, options, (err, doc, res) => {
if (doc === null)
return response.status(404).send({
error: 'Product not found'
})
return response.status(204).send();
});
}
catch (err) {
return response.status(400).send({
error: 'Product update failed'
});
}
}
Mongoose is returning null for the first object, _ID 0, in the DB even though it exists. I can retrieve the first post just fine using similar code, but I can not retrieve the first tag. I can retrieve all of my data with the exception of the first Tag, tag1 for the first post. If I add a second post with tags: tag4, tag5 and tag6 I can retrieve all of the data for the second post including tag4. If I point to tag1 in a later post it cannot be retrieved.
I have done some of searching and know that null being returned means that the record can't be found. I have also tried using find and findOne in addition to findById and get the same result. I can't figure out where I'm going wrong if the record exists. I'm sure there is probably a better way of doing it.
Thanks for the help.
I'm using mongoose, mongoose-simpledb, and tungus with node-webkit.
Save Tags:
$.each(tagArray, function(i, t) {
var tags = db.Tags();
tags.nextCount(function(err, count) {
post.tags.push(count + i); //tag ID to post
});
tags.text= tagArray[i]; //tag text
post.nextCount(function(err, count) {
tags.posts.push(count); //post ID to tag
});
tags.save(function(err) {
if (err) {throw err;}
console.log('tag saved');
});
});
Find Tags:
$.each(results[i].tags, function(j, t) {
db.Tags.findById(results[i].tags[j], function(err, tag) {
if (err) {return console.error(err);}
if (!tag) {return console.log('Could not find tag...');}
postContent += '' + tag.text + ' ';
});
});
Or if I use the following tag2 is returned instead of tag1 which is what should be returned.
db.Posts.
find({}).
populate('tags').
exec(function(error, post) {
console.log(post[0].tags[0].text);
});
Tags Model
var ObjectId = require('mongoose-simpledb').Types.ObjectId;
exports.schema = {
_id: Number,
text: String,
posts: [{type: ObjectId, ref: 'Posts', index: true}]
};
Tags DB
{"k":"0000000078","o":"0000000061","v":"001"}
{"_id":0,"_uid":1,"_dt":1409869458919,"_s":"c245621efdb176d3f4dd2db749590730"}
{"_id":0,"text":"Tag1","posts":[{"$wrap":"$oid","v":0}],"__v":0}
{"k":"0000000078","o":"0000000061","v":"001"}
{"_id":1,"_uid":1,"_dt":1409869458921,"_s":"80bc4453ee777de0177b27ba76ddc859"}
{"_id":1,"text":"Tag2","posts":[{"$wrap":"$oid","v":0}],"__v":0}
{"k":"0000000078","o":"0000000061","v":"001"}
{"_id":2,"_uid":1,"_dt":1409869458930,"_s":"12682b1c27ea57e8c09b87a2a6605510"}
{"_id":2,"text":"Tag3","posts":[{"$wrap":"$oid","v":0}],"__v":0}
Using find on the Tags DB:
db.Tags.findOne({
_id: '0'
}, function(err, result) {
if (err) return console.error(err);
console.dir('findOne: ' +result.text);
//throws error - Cannot read property 'text' of null
});
db.Tags.
findById(0, function(err, tag) {
if (err) return console.error(err);
console.log('tag by id: ' + tag);
//returns - null
});
db.Tags.
find({}).
exec(function(error, tag) {
console.log("find:" + tag[0]);
//returns - ( _id: 0, text: 'Tag1', _v: 0, posts: [0] )
//correctly finding the tag but not by id
});
If I change these find functions to find any tag other than the first tag, the correct information is returned.
The issue turned out to be mongoose-simpledb. I'm not sure if the issue is a bug in mongoose-simpledb or a conflict with tungus, but the "same" code works fine once mongoose-simpledb is removed.
I'm getting a duplicate document when using the mongodb-native-driver to save an update to a document. My first call to save() correctly creates the document and adds a _id with an ObjectID value. A second call creates a new document with a text _id of the original ObjectID. For example I end up with:
> db.people.find()
{ "firstname" : "Fred", "lastname" : "Flintstone", "_id" : ObjectId("52e55737ae49620000fd894e") }
{ "firstname" : "Fred", "lastname" : "Flintstone with a change", "_id" : "52e55737ae49620000fd894e" }
My first call correctly created Fred Flinstone. A second call that added " with a change" to the lastname, created a second document.
I'm using MongoDB 2.4.8 and mongo-native-driver 1.3.23.
Here is my NodeJS/Express endpoint:
app.post("/contacts", function (req, res) {
console.log("POST /contacts, req.body: " + JSON.stringify(req.body));
db.collection("people").save(req.body, function (err, inserted) {
if (err) {
throw err;
} else {
console.dir("Successfully inserted/updated: " + JSON.stringify(inserted));
res.send(inserted);
}
});
});
Here is the runtime log messages:
POST /contacts, req.body: {"firstname":"Fred","lastname":"Flintstone"}
'Successfully inserted/updated: {"firstname":"Fred","lastname":"Flintstone","_id":"52e55737ae49620000fd894e"}'
POST /contacts, req.body: {"firstname":"Fred","lastname":"Flintstone with a change","_id":"52e55737ae49620000fd894e"}
'Successfully inserted/updated: 1'
Why doesn't my second update the existing record? Does the driver not cast the _id value to an ObjectID?
What you are posting back the 2nd time contains a field named "_id", and it's a string. That is the problem.
Look at the document, what the save method does is a "Simple full document replacement function". I don't use this function quit often so here's what I guess. The function use the _id field to find the document and then replace the full document with what you provided. However, what you provided is a string _id. Apparently it doesn't equal to the ObjectId. I think you should wrap it to an ObjectId before passing to the function.
Besides, the save method is not recommended according to the document. you should use update (maybe with upsert option) instead
I don't exactly know why a second document is created, but why don't you use the update function (maybe with the upsert operator)?
An example for the update operation:
var query = { '_id': '52e55737ae49620000fd894e' };
db.collection('people').findOne(query, function (err, doc) {
if (err) throw err;
if (!doc) {
return db.close();
}
doc['lastname'] = 'Flintstone with a change';
db.collection('people').update(query, doc, function (err, updated) {
if (err) throw err;
console.dir('Successfully updated ' + updated + ' document!');
return db.close();
});
});
And now with the upsert operator:
var query = { '_id': '52e55737ae49620000fd894e' };
var operator = { '$set': { 'lastname': 'Flintstone with a change' } };
var options = { 'upsert': true };
db.collection('people').update(query, operator, options, function (err, upserted) {
if (err) throw err;
console.dir('Successfully upserted ' + upserted + ' document!');
return db.close();
});
The difference is that the upsert operator will update the document if it exist, otherwise it will create a new one. When using the upsert operator you should keep in mind that this operation can be underspecified. That means if your query does not contain enough information to identify a single document, a new document will be inserted.