How to add some action after document update in mongoose - javascript

E.g. I have field lastModified and when object is modified this field should be updated with current date. How can I do this with mongoose.js?
Schema example:
var schema = new Schema({
someFieldForUpdates: {
type: String,
},
//when 1st field changed this one should be updated too
lastModified: {
type: Date,
default: Date.now()
}
});

I think it could be done with middlewares like post-save or pre-save maybe
http://mongoosejs.com/docs/middleware.html
schema.post('save', function (doc) {
console.log('%s has been saved', doc._id);
// update another field
})

Related

When does mongoose schema update its keys?

I originally had this mongoose schema:
const scriptsSchema = mongoose.Schema({
title: {
type: String,
require: true
},
updated: {
type: Date,
default: Date.now(),
require: true
}
});
export default mongoose.models.Script || mongoose.model('Script', scriptsSchema)
That i updated to
const constructDate = () => {
var today = new Date();
return (today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate())
}
const scriptsSchema = mongoose.Schema({
title: {
type: String,
require: true
},
updated: {
type: Date,
default: constructDate(),
require: true
}
});
export default mongoose.models.Script || mongoose.model('Script', scriptsSchema)
Since I wanted to have only the date (no time) as a default value in the 'updated' field. Problem is, the change does not reflect and whenever I create a new script document, the default 'updated' value is still Date.now().
I tried dropping the collection, resetting the database... but somehow it doesen't notice the changes I've made in the schema.
My problem is not about updating the existing documents, since I am still in developement phase and I don't mind dropping all documents in the database. I just want the schema to update to its new version.

can't update boolean mongoose object with a time stamp

I am having trouble updating a boolean object and i am getting error every time-
this is the object-
const mongoose = require('mongoose');
const PlantProductSchema = new mongoose.Schema({
waterMotor: {
state: {
type: Boolean,
default: false,
time: { type: Date, default: Date.now },
}
},
});
module.exports = mongoose.model('PlantProduct', PlantProductSchema);
this is the update action-
plantProduct.waterMotor.update({state: idResultsObj.motorState });
idResultsObj.motorStat is boolean i chacked
on the other hand when I change I do this -
plantProduct.waterMotor.state = idResultsObj.motorState;
it works but It doesn't give a time stamp.
I appreciate any help I get!!
you are not using the update function correctly. it accepts two arguments, the first is the document to be updated and the second is the action.
you need to pass the id (or any other field like username) of the document that is being updated as the first argument.
plantProduct.waterMotor.update({_id: id}, {state: idResultsObj.motorState });
These are a couple of API which mongoose support for an update operation.
Regarding your code you have used update in the wrong way. update is the property of the Model object that why you are getting undefined. thought said below is the query that might help you.
const mongoose = require('mongoose');
const PlantProductSchema = new mongoose.Schema({
waterMotor: {
state: {
type: Boolean,
default: false,
time: {
type: Date,
default: Date.now
},
}
},
});
const ProductModel = mongoose.model('PlantProduct', PlantProductSchema);
const filterQuery = {}
const updateQuery = {
$set: {
"waterMotor.state": true
}
}
ProductModel.update(filterQuery, updateQuery, function(error, result) {
if (error) {
console.log(error)
} else {
console.log(response)
}
})

getting error ValidatorError: Path `id` is required. in mongo DB

I am getting this error while saving my document on collection
ValidatorError: Path id is required.
here is my code
https://codesandbox.io/s/lively-tree-hd0fo
const BlogPost = new Schema({
id: { type: String, required: true, unique: true },
empid: String,
date: Date
});
BlogPost.pre("save", function(next) {
var blog = this;
console.log();
var data = `${blog.empid}-${blog.date}`;
blog.id = crypto
.createHash("md5")
.update(data)
.digest("hex");
next();
});
getting the error when I am trying to save data.
a
pp.get("/saveData", async () => {
try {
var blog = new BlogPostModel({
empid: "test123",
date: "19-Jul-2019"
});
console.log("before save");
let saveBlog = await blog.save(); //when fail its goes to catch
console.log(saveBlog); //when success it print.
console.log("saveBlog save");
} catch (error) {
console.log(error);
}
});
In Validation docs, it says:
Validation is middleware. Mongoose registers validation as a pre('save') hook on every schema by default.
And in Save/Validate Hooks, it says:
The save() function triggers validate() hooks, because mongoose has a built-in pre('save') hook that calls validate(). This means that all pre('validate') and post('validate') hooks get called before any pre('save') hooks.
So it will validate before your pre('save') hook and give the error because you didn't provide the required field. You can solve it by changing pre('save') to pre('validate').
Editted sandbox: https://codesandbox.io/s/agitated-lederberg-rth1k
when you are saving your blog documents you are not passing id field.
id is automatically generated by mongo, you don't need to specify it.
Remove id from BlogPost schema.
But if you want to give your own id then pass a unique id.
const BlogPost = new Schema({
empid: String,
date: Date
});

Mongoose/MongoDb ,how to validate an array of Ids against another model

I have 2 moongose Schema:
var Schema2 = new Schema({
creator : { type: String, ref: 'User'},
schema_name : [{ type: String}],
});
var Schema1 = new Schema({
creator : { type: String, ref: 'User'},
schema_ref : [{ type: String, ref: 'Schema2' }],
});
Would like to know which is the best practice when I create a new Schema1 check that every element of array schema_ref, have the same creator.
Because schema1 elements are added by client form and so i have to check that the schema_ref elements are owned by same User that send the form
You can try with either validator function, or with a simple 'save' middleware:
Schema1.pre('save', function(next) {
let owner;
for (let entry in this.schema_ref) {
if (!owner) {
owner = entry;
} else {
if (entry !== owner) {
return next(new Error("owner mismatch");
}
}
}
});
Also, your schema might not work as you expect it to, it looks like you actually need:
schema_ref: [{
type: {type: String},
ref: "User"
}]
Additionally, take a look at id-validator plugin, or some similar to that - it will, in addition to your validation, also check that all ref-type properties like this actually exist in the other (Users) collection.

Mongoose find/update subdocument

I have the following schemas for the document Folder:
var permissionSchema = new Schema({
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
});
var folderSchema = new Schema({
name: { type: string },
permissions: [ permissionSchema ]
});
So, for each Page I can have many permissions. In my CMS there's a panel where I list all the folders and their permissions. The admin can edit a single permission and save it.
I could easily save the whole Folder document with its permissions array, where only one permission was modified. But I don't want to save all the document (the real schema has much more fields) so I did this:
savePermission: function (folderId, permission, callback) {
Folder.findOne({ _id: folderId }, function (err, data) {
var perm = _.findWhere(data.permissions, { _id: permission._id });
_.extend(perm, permission);
data.markModified("permissions");
data.save(callback);
});
}
but the problem is that perm is always undefined! I tried to "statically" fetch the permission in this way:
var perm = data.permissions[0];
and it works great, so the problem is that Underscore library is not able to query the permissions array. So I guess that there's a better (and workgin) way to get the subdocument of a fetched document.
Any idea?
P.S.: I solved checking each item in the data.permission array using a "for" loop and checking data.permissions[i]._id == permission._id but I'd like a smarter solution, I know there's one!
So as you note, the default in mongoose is that when you "embed" data in an array like this you get an _id value for each array entry as part of it's own sub-document properties. You can actually use this value in order to determine the index of the item which you intend to update. The MongoDB way of doing this is the positional $ operator variable, which holds the "matched" position in the array:
Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{
"$set": {
"permissions.$": permission
}
},
function(err,doc) {
}
);
That .findOneAndUpdate() method will return the modified document or otherwise you can just use .update() as a method if you don't need the document returned. The main parts are "matching" the element of the array to update and "identifying" that match with the positional $ as mentioned earlier.
Then of course you are using the $set operator so that only the elements you specify are actually sent "over the wire" to the server. You can take this further with "dot notation" and just specify the elements you actually want to update. As in:
Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{
"$set": {
"permissions.$.role": permission.role
}
},
function(err,doc) {
}
);
So this is the flexibility that MongoDB provides, where you can be very "targeted" in how you actually update a document.
What this does do however is "bypass" any logic you might have built into your "mongoose" schema, such as "validation" or other "pre-save hooks". That is because the "optimal" way is a MongoDB "feature" and how it is designed. Mongoose itself tries to be a "convenience" wrapper over this logic. But if you are prepared to take some control yourself, then the updates can be made in the most optimal way.
So where possible to do so, keep your data "embedded" and don't use referenced models. It allows the atomic update of both "parent" and "child" items in simple updates where you don't need to worry about concurrency. Probably is one of the reasons you should have selected MongoDB in the first place.
In order to validate subdocuments when updating in Mongoose, you have to 'load' it as a Schema object, and then Mongoose will automatically trigger validation and hooks.
const userSchema = new mongoose.Schema({
// ...
addresses: [addressSchema],
});
If you have an array of subdocuments, you can fetch the desired one with the id() method provided by Mongoose. Then you can update its fields individually, or if you want to update multiple fields at once then use the set() method.
User.findById(userId)
.then((user) => {
const address = user.addresses.id(addressId); // returns a matching subdocument
address.set(req.body); // updates the address while keeping its schema
// address.zipCode = req.body.zipCode; // individual fields can be set directly
return user.save(); // saves document with subdocuments and triggers validation
})
.then((user) => {
res.send({ user });
})
.catch(e => res.status(400).send(e));
Note that you don't really need the userId to find the User document, you can get it by searching for the one that has an address subdocument that matches addressId as follows:
User.findOne({
'addresses._id': addressId,
})
// .then() ... the same as the example above
Remember that in MongoDB the subdocument is saved only when the parent document is saved.
Read more on the topic on the official documentation.
If you don't want separate collection, just embed the permissionSchema into the folderSchema.
var folderSchema = new Schema({
name: { type: string },
permissions: [ {
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
} ]
});
If you need separate collections, this is the best approach:
You could have a Permission model:
var mongoose = require('mongoose');
var PermissionSchema = new Schema({
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
});
module.exports = mongoose.model('Permission', PermissionSchema);
And a Folder model with a reference to the permission document.
You can reference another schema like this:
var mongoose = require('mongoose');
var FolderSchema = new Schema({
name: { type: string },
permissions: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Permission' } ]
});
module.exports = mongoose.model('Folder', FolderSchema);
And then call Folder.findOne().populate('permissions') to ask mongoose to populate the field permissions.
Now, the following:
savePermission: function (folderId, permission, callback) {
Folder.findOne({ _id: folderId }).populate('permissions').exec(function (err, data) {
var perm = _.findWhere(data.permissions, { _id: permission._id });
_.extend(perm, permission);
data.markModified("permissions");
data.save(callback);
});
}
The perm field will not be undefined (if the permission._id is actually in the permissions array), since it's been populated by Mongoose.
just try
let doc = await Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{ "permissions.$": permission},
);

Categories

Resources