mongoose "_id" field can't be deleted - node.js

I want to return the "id" field instead of "_id",using the solution in
MongoDB: output 'id' instead of '_id'
the following is my code:
ScenicSpotSchema.virtual('id').get(function () {
var id = this._id.toString();
delete this._id;
delete this.__v;
return id;
});
But the response still have the field "id" and "_id",It seems that the delete doesn't take effect. Why?

I'm estimating what you need is toJSON. This should do what you want:
schema.options.toJSON = {
transform: function(doc, ret) {
ret.id = ret._id;
delete ret._id;
delete ret.__v;
}
};

On Mongoose, the "id" property is created by default and it's a virtual one returning the value of "_id". You don't need to do that yourself.
If you want to disable the automatic creation of the "id" property, you can do that when defining the schema:
var schema = new Schema({ name: String }, { id: false })
For the _id field, you can tell Mongoose not to create one by default when you create a new Mongoose object, with the {_id: false} property. HOWEVER, when you .save() the document in MongoDB, the server WILL create an _id property for you.
See: http://mongoosejs.com/docs/guide.html#_id
What I do in my code is creating an instance method called something like returnable that returns a plain JS object with just the properties I need. For example:
userSchema.methods.returnable = function(context) {
// Convert this to a plain JS object
var that = this.toObject()
// Add back virtual properties
that.displayName = this.displayName
// Manually expose selected properties
var out = {}
out['id'] = that._id
var expose = ['name', 'displayName', 'email', 'active', 'verified', 'company', 'position', 'address', 'phone']
for(var i in expose) {
var key = expose[i]
out[key] = that[key]
// Change objects to JS objects
if(out[key] && typeof out[key] === 'object' && !Array.isArray(out[key])) {
// Change empty objects (not array) to undefined
if(!Object.keys(out[key]).length) {
out[key] = undefined
}
}
}
return out
}

Related

Mongoose - remove _id from lean ( using toJSON() ?)

I'm having a NODE.JS project using mongoose 5.x
My model have toJSON method which removes the _id & __v fields perfectly
mySchema.method("toJSON", function toJSON() {
const {__v, _id, ...object} = this.toObject();
return {
id: _id,
...object
};
});
so when fetching data from the db:
const data = myModel.findOne({_id: id});
I get an object that when serialized to the user:
res.json(data);
It doesn't contain the _id and __v fields as required.
The problem is when I use lean():
const data = myModel.findOne({_id: id}).lean();
the data object contains those fields.
I can remove them manually when using lean
but I would prefer to find a way to sanitize the data object in both cases with the same mechanism.
any suggestions?
Thanks in advance.
Not sure if this is what you want but maybe:
const data = myModel.findOne({_id: id}).lean().then(res => {
delete res._id
return res
})
When JSON.stringify() is called on an object, a check if done if it has a property called toJSON. It's not specific to Mongoose, as it works on plain objects:
const myObj = {
_id: 123,
foo: 'bar',
// `toJSON() {...}` is short for `toJSON: function toJSON() {...}`
toJSON() {
// Because "this" is used, you can't use an arrow function.
delete this._id;
return this;
}
};
console.log(JSON.stringify(myObj));
// {"foo":"bar"}
Mongoose doesn't have an option to automatically inject a toJSON function into objects returned by lean(). But that's something you can add.
First, create a function that:
Takes an object with properties
Listens to when Mongoose runs a find query
Tells Mongoose that after the query, it should change the result
The change: merge the result with the object from step 1.
function mergeWithLeanObjects(props) {
// Return a function that takes your schema.
return function(schema) {
// Before these methods are run on the schema, execute a function.
schema.pre(['find', 'findOne'], function() {
// Check for {lean:true}
if (this._mongooseOptions.lean) {
// Changes the document(s) that will be returned by the query.
this.transform(function(res) {
// [].concat(res) makes sure its an array.
[].concat(res).forEach(obj => Object.assign(obj, props));
return res;
});
}
});
};
}
Now add it to your schema:
mySchema = new mongoose.Schema({
foo: String
});
mySchema.plugin(mergeWithLeanObjects({
toJSON() {
delete this._id;
delete this.__v;
return this;
}
}));
Then test it:
const full = await myModel.findOne();
const lean = await myModel.findOne().lean();
console.log(full);
// Logs a Mongoose document, all properties.
{ _id: new ObjectId("62a8b39466768658e7333154"), foo: 'bar', __v: 1 }
console.log(JSON.stringify(full));
// Logs a JSON string, all properties.
{"_id":"62a8b39466768658e7333154","foo":"bar","__v":1}
console.log(lean);
// Logs an Object, all properties.
{ _id: new ObjectId("62a8b39466768658e7333154"),
foo: 'bar', __v: 1, toJSON: [Function: toJSON] }
console.log(JSON.stringify(lean));
// Logs a JSON string, filtered properties.
{"foo":"bar"}
If you want to re-use the plugin with the same settings on multiple schemas, just save the function that mergeWithLeanObjects returns somewhere.
// file: clean.js
module.exports = mergeWithLeanObjects({
toJSON() {
delete this._id;
delete this.__v;
return this;
}
});
// file: some-schema.js
schema1.plugin(require('./clean.js'));
// file: some-other-schema.js
schema2.plugin(require('./clean.js'));
There's also mongoose.plugin() to add the function to all schemas.
Try this to retrieve the _id from a document
myModel.findOne({_id: id}, function(err, doc) {
if(err)
return 'do something with this err'
console.log(doc._id)
})

Mongoose overwriting data in MongoDB with default values in Subdocuments

I currently have a problem with updating data in MongoDB via mongoose. I have a nested Document of the following structure
const someSchema:Schema = new mongoose.Schema({
Title: String,
Subdocuments: [{
SomeValue: String
Position: {
X: {type: Number, default: 0},
Y: {type: Number, default: 0},
Z: {type: Number, default: 0}
}
}]
});
Now my problem is that I am updating this with findOneAndUpdateById. I have previously set the position to values other than the default. I want to update leaving the position as is by making my request without the Position as my frontend should never update it (another application does).
However the following call
const updateById = async (Id: string, NewDoc: DocClass) => {
let doc: DocClass | null = await DocumentModel.findOneAndUpdate(
{ _id: Id },
{ $set: NewDoc },
{ new: true, runValidators: true });
if (!doc) {
throw createError.documentNotFound(
{ msg: `The Document you tried to update (Id: ${Id}) does not exist` }
);
}
return doc;
}
Now this works fine if I don't send a Title for the value in the root of the schema (also if i turn on default values for that Title) but if I leave out the Position in the Subdocument it gets reset to the default values X:0, Y:0, Z:0.
Any ideas how I could fix this and don't set the default values on update?
Why don't you find the document by id, update the new values, then save it?
const updateById = async (Id: string, NewDoc: Training) => {
const doc: Training | null = await TrainingModel.findById({ _id: Id });
if (!doc) {
throw createError.documentNotFound(
{ msg: `The Document you tried to update (Id: ${Id}) does not exist` }
);
}
doc.title = NewDoc.title;
doc.subdocument.someValue = NewDoc.subdocument.someValue
await doc.save();
return doc;
}
check out the link on how to update a document with Mongoose
https://mongoosejs.com/docs/documents.html#updating
Ok after I gave this some thought over the weekend I got to the conclusion that the behaviour of mongodb was correct.
Why?
I am passing a document and a query to the database. MongoDb then searches Documents with that query. It will update all Fields for which a value was supplied. If for Title I set a new string, the Title will get replaced with that one, a number with that one and so on. Now for my Subdocument I am passing an array. And as there is no query, the correct behavioud is that that field will get set to the array. So the subdocuments are not updated but indeed initialized. Which will correctly cause the default values to be set. If I just want to update the subdocuments this is not the correct way
How to do it right
For me the ideal way is to seperate the logic and create a seperate endpoint to update the subdocuments with their own query. So to update all given subdocuments the function would look something like this
const updateSubdocumentsById= async ({ Id, Subdocuments}: { Id: string; Subdocuments: Subdocument[]; }): Promise<Subdocument[]> => {
let updatedSubdocuments:Subdocument[] = [];
for (let doc of Subdocuments){
// Create the setter
let set = {};
for (let key of Object.keys(doc)){
set[`Subdocument.$.${key}`] = doc[key];
}
// Update the subdocument
let updatedDocument: Document| null = await DocumentModel.findOneAndUpdate(
{"_id": Id, "Subdocuments._id": doc._id},
{
"$set" : set
},
{ new : true}
);
// Aggregate and return the updated Subdocuments
if(updatedDocument){
let updatedSubdocument:Subdocument = updatedTraining.Subdocuments.filter((a: Subdocument) => a._id.toString() === doc._id)[0];
if(updatedSubdocument) updatedSubdocuments.push(updatedSubdocument);
}
}
return updatedSubdocuments;
}
Been struggling with this myself all evening. Just worked out a really simple solution that as far as I can see works perfectly.
const venue = await Venue.findById(_id)
venue.name = name
venue.venueContact = venueContact
venue.address.line1 = line1 || venue.address.line1
venue.address.line2 = line2 || venue.address.line2
venue.address.city = city || venue.address.city
venue.address.county = county || venue.address.county
venue.address.postCode = postCode || venue.address.postCode
venue.address.country = country || venue.address.country
venue.save()
res.send(venue)
The result of this is any keys that don't receive a new value will just be replaced by the original values.

Mongoose not persisting returned object

Mongo: 3.2.1.
I have a model defined as such:
var MySchema = new Schema(
{
....
records: {type: Array, "default": []};
I first create an object based on that schema with no record field and it's correctly added to the database. I then update that object as such:
Client
angular.extend(this.object.records, [{test: 'test'}]);
this.Service.update(this.object);
Server (omitting the none-problematic code)
function saveUpdates(updates) {
return function(entity) {
var updated = _.merge(entity, updates);
return updated.save()
.then(updated => {
console.log(updated);
Model.find({_id: updated._id}).then((data)=> console.log(data));
return updated;
});
};
}
The first console.log prints the object with records field updated. The second prints the object without. What am I missing? How can the resolved promise be different than the persisted object? Shouldn't data and updated be identical?
I think you have a couple problems.
You are using the variable 'updated' twice.
var updated = _.merge(entity, updates); // declared here
return updated.save()
.then(updated => { // trying to re-declare here
The other issue might be that you are trying to merge the 'updates' property with the mongo object and not the actual object values. Try calling .toObject() on your mongo object to get the data.
function saveUpdates(updates) {
return function(entity) {
// call .toObject() to get the data values
var entityObject = entity.toObject();
// now merge updates values with the data values
var updated = _.merge(entityObject, updates);
// use findByIdAndUpdate to update
// I added runValidators in case you have any validation you need
return Model
.findByIdAndUpdate(entity._id, updated, {
runValidators: true
})
.exec()
.then(updatedEntity => {
console.log(updatedEntity);
Model.find({_id: entity._id})
.then(data => console.log(data));
});
}
}

How to apply function for all Collection

I want to update my collection in server.js by using a function.
When I change one field I need to change multiple collections.
My question is how can I use a parameter as a Collection name. Is there any way for it or I must write a function for each Collection?
update: function(personID,option) {
return Personel.update(
{ id: personID },
{ $set: option },
{ multi: true }
);
},
I want to apply this logic for separate collections.
There is a trickier workaround for this problem. you need to actually bind all of your collection in a single object.
CollectionList = {};
CollectionList.Personel = new Mongo.Collection('personel');
CollectionList.secondCollection = new Mongo.Collection('second');
after that you pass as your collection name as a string into the method.
update: function(collectionName,personID,option){
return CollectionList[collectionName].update(
//..rest of your code
);
You can try this approach:
var Personel = new Mongo.Collection('personel');
var Items = new Mongo.Collection('items');
var SomeOtherCollection = new Mongo.Collection('someOtherCollection');
....
update: function(personID, option, collectionName) {
// Choose collection by given name
var Collection = {
Personel: Personel,
Items: Items,
SomeOtherCollection: SomeOtherCollection
}[collectionName];
return Collection.update(
{ id: personID },
{ $set: option },
{ multi: true }
);
},

How to remove mongo specific fields from result (NodeJS, Mongoose)

I want to remove all Mongo specific fields (like '_id') from query result. Is there a simple method to do this or should I remove fields manually? If yes, then which are that fields and how to do that?
I'm using NodeJS and Mongoose
You can use select() method for remove the field from your query:
Model.find({}).select("-removed_field").then (resp => {
// your code
});
You should specified the "-" before field name, to be remove this field.
If you want remove several fields - you can specified their as array:
Model.find({}).select(["-removed_field1", "-removed_field2" ... ]).then (resp => {
// your code
});
Also you can to select only specified fields, using this method without "-"
Model.find({}).select(["field1", "field2" ... ]).then (resp => {
// your code
});
If you want hide _id property you can use text argument with prefix - which will exclude this or that field from the result, for get sepecifict fields you should pass like this:
Entity.find({ ... }, 'field1 field2', function(err, entity) {
console.log(entity); // { field1: '...', field2: '...' }
});
You can specify a field to be excluded from results by using the optional 2nd parameter projection string of the find method:
Model.find({}, "-a -b").then (res => {
// objects in the res array will all have the
// 'a' and 'b' fields excluded.
});
https://mongoosejs.com/docs/api.html#model_Model.find (see projection)
you can use mongoose instance method two show specific fields from all documents
const userSchema = new mongoose.Schema({
email: {
type: String,
},
name: {
type: String,
maxlength: 128,
index: true,
trim: true,
},
});
userSchema.method({
transform() {
const transformed = {};
const fields = ['name', 'email'];
fields.forEach((field) => {
transformed[field] = this[field];
});
return transformed;
},
});
module.exports = mongoose.model('User', userSchema);
if You want to remove any specific fields like _id, You can try in two ways:
Suppose Here you try to find a user using User Model
User.find({ email: email }, { _id: 0 });
OR
const user = User.find({ email: email });
delete user._doc._id;
OP mentioned "from result", as far as I understood, it means, removing from the query result i.e. query result will contain the field, but will be removed from the query result.
A SO answer here mentions, that to modify a query result (which are immutable), we've to convert the result to Object using toObject() method (making it mutable).
To remove a field from a query result,
let immutableQueryResult = await Col.findById(idToBeSearched)
let mutableQueryResult = immutableQueryResult.toObject()
delete mutableQueryResult.fieldToBeRemoved
console.log(mutableQueryResult)
Another way of getting the mutable result is using the _doc property of the result:
let immutableQueryResult = await Col.findById(idToBeSearched)
let mutableQueryResult = immutableQueryResult._doc // _doc property holds the mutable object
delete mutableQueryResult.fieldToBeRemoved
console.log(mutableQueryResult)

Resources