Can anyone explain how to use the updateOne pre-hook, in mongoose (5.9.5)?
I am need to create a normalised 'shadow field' (not sure the right term) to help with certain searches. While I am able to update the shadow field during a save, I am having trouble during update.
The save pre-hook:
personSchema.pre('save', function (next) {
if (this.isModified('name')) {
const name = this.name;
if (name && name.trim().length > 0) {
const shadowName = name.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
this.shadowName = shadowName.toLowerCase();
} else {;
this.shadowName = name;
}
}
// do stuff
next();
});
Doing the equivalent for updateOne does not seem to work (shadowName stays with the value it was given during the initial save):
personSchema.pre('updateOne', function (next) {
const name = this.name;
if (name && name.trim().length > 0) {
const shadowName = name.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
this.update({}, { shadowName: shadowName.toLowerCase() });
} else {
this.shadowName = name;
}
// do stuff
next();
});
The schema:
const personSchema = new mongoose.Schema({
resourceId: {
type: String,
required: true,
unique: true,
index: true,
uppercase: true
},
name:{
type: String,
required:true,
index: true
},
// can be used for searches, but don't update directly
shadowName: {
type: String,
index: true
},
});
BTW I can confirm the hook is called, but the field is not updated.
Turns out you can't access the field values directly and instead need to leverage the get() and set() methods on the query.
Changing the pre-updateOne hook to be the following works:
personSchema.pre('updateOne', function (next) {
const name = this.get('name');
if (name && name.trim().length > 0) {
const shadowName = name.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
this.set('shadowName', shadowName.toLowerCase());
} else {
this.set('shadowName', name);
}
// do stuff
next();
});
Related
This is the User schema in mongoose:
var userSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
},
name: {
type: String,
required: true,
},
Addtasks: [
{
topic: String,
words: Number,
keywords: String,
website: String,
otherdetails: String,
exampleRadios: String,
deadline: Date,
Date: String,
fileName: String,
Bigpaths: [],
},
],
});
module.exports = mongoose.model('User', userSchema);
I want to use/access the Bigpaths array, which is defined inside the Addtasks array, which is defined in User. Data is already are there in mongoDB, which I have inserted via UI page. I am trying the following code but I am getting this error in console:
data.Addtasks[Object.keys(data.Addtasks).length - 2].Bigpaths.forEach(
(element) => {
// ...
}
)
as
TypeError: Cannot read property 'Bigpaths' of undefined
at \Desktop\grumpytext\routes\index.js:99:71
Code:
const { files } = req;
User.findOne({ email: req.user.email }, function (error, data) {
if (error) {
console.log('Three');
} else if (data) {
if (Object.keys(data.Addtasks).length > 1) {
data.Addtasks[Object.keys(data.Addtasks).length - 2].Bigpaths.forEach(
(element) => {
files.forEach((currentElement) => {
if (element.name == currentElement.filename) {
files.pull(currentElement.filename);
}
});
}
);
}
}
});
How to resolve this error or how to access all the elements of Bigpaths array so that I can iterate it with forEach loop?
I'm not sure here, but I think you need to populate Addtasks prior to manipulating it:
const files = req.files;
User.findOne({email:req.user.email}).populate('Addtasks').exec((error, data) => {
if (error) {
console.log("Three");
}
else
{
if(data)
{
if(Object.keys(data.Addtasks).length > 1)
{
console.log("Addtasks count: " + Object.keys(data.Addtasks).length);
data.Addtasks[Object.keys(data.Addtasks).length - 2].Bigpaths.forEach(element => {
files.forEach(currentElement => {
if(element.name == currentElement.filename)
{
files.pull(currentElement.filename);
}
})
});
}
}
}
});
Please notice the log console.log("Addtasks count: " + Object.keys(data.Addtasks).length); - in case the solution does not work, I advise to add some prints, especially to check if the count of elements is as expected or properties within an object are fine.
I have two Mongoose model schemas as follows. The LabReport model contains an array of the referenced SoilLab model. There is a static method in the SoilLab model that I was using to select which fields to display when LabReport is retrieved.
//LabReport.js
var mongoose = require("mongoose");
var SoilLab = mongoose.model("SoilLab");
var LabReportSchema = new mongoose.Schema(
{
labFarm: { type: mongoose.Schema.Types.ObjectId, ref: "Farm" },
testName: { type: String },
soilLabs: [{ type: mongoose.Schema.Types.ObjectId, ref: "SoilLab" }],
},
{ timestamps: true, usePushEach: true }
);
LabReportSchema.methods.toLabToJSON = function () {
return {
labReport_id: this._id,
testName: this.testName,
soilLabs: this.soilLabs.SoilToLabJSON(),
};
};
mongoose.model("LabReport", LabReportSchema);
//SoilLab.js
var mongoose = require("mongoose");
var SoilLabSchema = new mongoose.Schema(
{
description: { type: String },
sampleDate: { type: Date },
source: { type: String },
},
{ timestamps: true, usePushEach: true }
);
SoilLabSchema.methods.SoilToLabJSON = function () {
return {
description: this.description,
sampleDate: this.sampleDate,
source: this.source,
};
};
mongoose.model("SoilLab", SoilLabSchema);
When I try to retrieve the LabReport, I get "this.soilLabs.SoilToLabJSON is not a function". This is how I'm trying to retrieve LabReport.
//labReports.js
...
return Promise.all([
LabReport.find()
.populate("soilLabs")
.exec(),
LabReport.count(query).exec(),
req.payload ? User.findById(req.payload.id) : null,
]).then(function (results) {
var labReports = results[0];
var labReportsCount = results[1];
var user = results[2];
return res.json({
labReports: labReports.map(function (labReport) {
return labReport.toLabToJSON(user); //This cant find SoilToLabJSON
}),
If I remove the .SoilToLabJSON in LabReport.js and just call this.soilLabs, it works but outputs all of the soilLabs data which will become an issue when I have the model completed with more data. I have dug into statics vs methods a little and tried changing it to statics but it didn't work.
I get the soilLabs to populate but not sure why the .SoilToLabJSON method is inaccessible at this point. Do I need to find() or populate the soilLab differently? Is the method incorrect?
labReport.toLabToJSON is passing an array and that was causing the error for me. I simply edited the LabReport.js to the following to take the array and map it to SoilToLabJSON properly.
myTestSoilLabOutput = function (soilLabs) {
var test = soilLabs.map(function (soilLab) {
return soilLab.SoilToLabJSON();
});
return test;
Changed the LabReportSchema.methods.toLabToJSON to:
LabReportSchema.methods.toLabToJSON = function () {
return {
labReport_id: this._id,
testName: this.testName,
soilLabs: myTestSoilLabOutput(this.soilLabs),
};
};
Problem with a custom validator in node.js, using mongoose. I'm trying to check if a query exists in headerLog prior to inserting it.
My code is below:
var mongoose = require('mongoose'); //layer above mongodb
var Schema = mongoose.Schema;
var headerLogSchema = new Schema({
query: { type: String, required: true, unique: true, validate: {
validator: function(v, cb) {
HeaderLog.find({query: v}, function(err, documents){
cb(documents.length == 0);
});
},
message: 'Header already exists in log, didnt save this one.'
}
}
})
var HeaderLog = mongoose.model('headerLog', headerLogSchema);
module.exports = HeaderLog;
The error: TypeError: cb is not a function.
I'm calling this function like so:
function logHeader(query) {
var newHeaderLog = new HeaderLog({
query: query
})
newHeaderLog.save(function(err) {
if (err) {
console.log(err);
}
else {
console.log('New header logged');
}
});
}
What am I doing wrong?
As the reference states, asynchronous validators should either have isAsync flag:
validate: {
isAsync: true,
validator: function(v, cb) { ... }
}
Or return a promise. Since the validator already uses another model, and Mongoose models are promise-based, it makes sense to use existing promise:
validator: function(v) {
return HeaderLog.find({query: v}).then(documents => !documents.length);
}
countDocuments is a better alternative to find for cases when only documents count is needed.
If you look at the async validator example here in the doc, it looks like you have to pass the option isAsync: true in order to tell mongoose that you are using an async validator and thus it should pass a callback to it.
var headerLogSchema = new Schema({
query: {
type: String,
required: true,
unique: true,
validate: {
isAsync: true, // <======= add this
validator: function(v, cb) {
HeaderLog.find({query: v}, function(err, documents){
cb(documents.length == 0);
});
},
message: 'Header already exists in log, didnt save this one.'
}
}
})
how would I achieve a GraphQL Mutation in nodeJS that has arguments which are optional?
My current mutation has an args field, but all the arguments are mandatory. Since I couldn't find anything in the documentation, I don't know how or if this is possible.
This is my current code:
const fakeDB = {
count: 0
};
const schema = new GraphQLSchema({
query: //...
mutation: new GraphQLObjectType({
name: 'adsF',
fields: {
updateCount: {
type: GraphQLInt,
args: {
count: { type: GraphQLInt } // I want to make this argument optional
},
resolve: (value, { count }) => {
// Catch if count is null or undefined
if (count == null) {
// If so just update with default
fakeDB.count = 5;
} else {
fakeDB.count = count
}
return fakeDB.count;
})
}
}
})
});
Thanks for Your help!
Types in GraphQL are nullable by default. That means that the way you have specified the mutation at the moment makes the count optional. If you wanted a field to be mandatory you need to mark it as non null
In my express/mongoose code when I update a nested var it deletes any other nested vars in the object I didn't update?
My schema:
var MySchema = new Schema({
active: { type: Boolean, required: true, default: true },
myvar: {
useDefault: { type: Boolean, required: true },
custom: { type: String },
default: { type: String }
}
});
My express middleware update function:
var updateData = req.body;
MySchema.findOneAndUpdate({ active: true }, updateData, function(err, myschema) {
if (err) throw err;
if (!myschema) { response(403, { success:false, message: "myschema not updated." }, res); }
// success
response(200, { success:true, message: "myschema updated." }, res);
});
The record initially look like this:
{
"myvar": {
"useDefault": true,
"custom": "some custom var",
"default": "some value"
}
}
When I submit an update to say the myvar.useDefault value the record ends up like this:
{
"myvar": {
"useDefault": false
}
}
Can anyone advise how to update only the targeted var and leave any other vars as they were?
The answer was to convert the response.body object to dot notation and pass that modified object to mongoose findOneAndUpdate. Thanks to #btkostner on Gitter for the answer and his toDot function. Here is the relevant bits of code:
function(req, res) {
// convert response to dot notation so objects retain other values
var dotData = toDot(req.body, '.');
MySchema.findOneAndUpdate({ active: true }, dotData, function(err, myschema) {
...
}
// author: #btcostner
function toDot (obj, div, pre) {
if (typeof obj !== 'object') {
throw new Error('toDot requires a valid object');
}
if (pre != null) {
pre = pre + div;
} else {
pre = '';
}
var iteration = {};
Object.keys(obj).forEach(function(key) {
if (_.isPlainObject(obj[key])) {
Object.assign(iteration, _this.toDot(obj[key], div, pre + key));
} else {
iteration[pre + key] = obj[key];
}
});
return iteration;
};
NOTE: I had to convert the toDot() function to ES5 for my project, here is the original ES6 version: github.com/elementary/houston/blob/master/src/lib/helpers/dotNotation.js