Async instance method - node.js

Async instance method
I want to add an instance method to my model Reservation, this instance method will perform a query on the relations model and execute some calculations on it. However, return a value asynchronously ain't possible.
I have a solution which returns a Promise, but that really ain't feasible when having multiple instance methods to return as a json object.
Controller
// controllers/ParkingController
reservationsCount: function(req, res, next){
Parking.find('5751401d54f4ca110020c15b').exec(function(err, parking) {
console.log(parking.reservationsCount())
res.json({});
});
}
Model
// models/Parking
module.exports = {
attributes: {
name: { type: 'string'},
reservations: {
collection: "reservation",
via: "parking",
},
getReservationsCount: function(cb) {
return Parking.findOne(this.id).populate('reservations').exec(function(err, result){
cb(result.reservations.length);
});
},
}
};

Try this (not tested)
Model
getReservationsCount: function() { // no callback since we use Promises
return Parking.findOne(this.id)
.populate('reservations')
.then(function(parking){
return parking.reservations.length;
});
},
Controller
reservationsCount: function(req, res){ // next is useless in sails
Parking.findOne('5751401d54f4ca110020c15b') // findOne to get one object
.then(function(parking) {
return parking.getReservationsCount(); // getReservationsCount returns a Promise
})
.then(function(reservationsCount){
return res.json({reservationsCount});
})
.catch(function(err){
sails.log.error(err);
// handle error
});
}

Related

Response of an Returning Promise is not working: undefined Variable

Hi I am wondering that my response of a Promise is not working. I always get "undefined" Variable if want to get access in the .then function.
Here´s my example:
class BookingRoute extends BasicRoute{
constructor(schemaModel) {
super();
Schema = schemaModel;
}
async post(req, res){ // Example for Override
super.post(Schema, req, res, StoredBookings)
.then((result) => {
console.log(result);
res.json(result);
})
.catch((error) => {
console.log(error);
res.status(422).send("Error while router.get api/products");
});
}
class BasicRoute {
async post(Schema, req, res, storedItems) {
console.log("post " + Schema.modelName);
(req.body != null ? null : res.status(422).send("Error while router.get api/xxx"));
let schema = new Schema(req.body);
await schema
.save()
.then((result)=> {
storedItems.push(result);
return new Promise((resolve, reject)=>{
console.log("Datay from Array...");
console.log(storedItems);
resolve(storedItems);
})
})
.catch(error =>{
return new Error("Wrong Parameter router.get api/xxx/parameters");
})
}
}
I am wondering, because the output of the stored Items is working fine.
But the output of the result is always "undefined".
I have following console output:
post Bookings
Datay from Array...
[
{
Products: [ 1000, 2000 ],
StartDate: '31.05.2021',
EndDate: '31.05.2021',
StartTime: 10,
EndTime: 5,
TotalPrice: 4,
OrderNumber: 10,
__v: 0
}
]
undefined
Does somebody has any idea please?
Thank you
In JavaScript or TypeScript, classes don't expected a function as a member.
I'm using VS Code as my code editor, and the terminal shows me an error:
Unexpected token. a constructor method accessor or property was expected.
This error points to your class class BasicRoute
So, the problem is async post().
You need to bind to a method instead.
Hope you can think in a solution. If not, reply me to help you.

Modifying mongoose document on pre hook of findOneAndUpdate()

I have a mongoose schema as such
var schema = new Schema({
id:Number
updated_at:Date
});
I am using findOneAndUpdate() to update this model as such
model.findOneAndUpdate(
{ id: json.id },
json,
{
upsert: true,
runValidators: true
})
.then(() => {
recordsUpdated++;
})
.catch((err) => {
this.emit('error', err);
});
The value being passed in json is not correct and I need to make some modifications to it. I am looking for a pre hook to do the modification. I have tried
faction.pre('findOneAndUpdate', function (next) {
this.update({ $set: { updated_at: this.getUpdate().updated_at * 1000 } });
next();
});
In short I want to convert the timestamp which is in seconds to milliseconds before updating the database, but this doesn't work.
After blindly throwing stones all around, what worked for me was
schema.pre('findOneAndUpdate', function (next) {
this._update.updated_at *= 1000;
next();
});
In short one needs to modify the document present in the _update property.
Better solution is like this:
schema.pre('findOneAndUpdate', function (this, next) {
this.update().updated_at *= 1000;
next();
});
Update "mongoose": "^6.4.0"
Thanks to the above top-rated solution, what worked for me was
this._update.$set.updated_at = new Date();
In my case, I wanted to save the currency key as uppercase if it is available in the requested object.
schema.pre('findOneAndUpdate', async function (next) {
const currency = this?._update?.$set?.currency;
if (currency) {
this.set({ currency: currency.toUpperCase() });
}
next();
});

"pre" and "post" remove Middleware not firing

I have implemented two different ways to remove a user and not one of them fires the "pre" and "post" remove Middleware.
As I understand
Below are my two different implementations in my model file:
Method One:
var User = module.exports = mongoose.model('User', userSchema);
userSchema.pre('remove', function(next) {
// 'this' is the client being removed. Provide callbacks here if you want
// to be notified of the calls' result.
//Vouchers.remove({user_id: this._id}).exec();
console.log("pre test");
next();
});
userSchema.post('remove', function(next) {
// 'this' is the client being removed. Provide callbacks here if you want
// to be notified of the calls' result.
//Vouchers.remove({user_id: this._id}).exec();
console.log("post test");
next();
});
// Remove User
module.exports.removeUser = function(id, callback){
var query = {_id: id};
console.log('test');
//User.remove(query, callback);
User.find(query).remove(callback);
}
Method Two:
var User = module.exports = mongoose.model('User', userSchema);
userSchema.pre('remove', function(next) {
// 'this' is the client being removed. Provide callbacks here if you want
// to be notified of the calls' result.
//Vouchers.remove({user_id: this._id}).exec();
console.log("pre test");
next();
});
userSchema.post('remove', function(next) {
// 'this' is the client being removed. Provide callbacks here if you want
// to be notified of the calls' result.
//Vouchers.remove({user_id: this._id}).exec();
console.log("post test");
next();
});
// Remove User
module.exports.removeUser = function(id, callback){
var query = {_id: id};
console.log('test');
User.remove(query, callback);
}
This is how I got everything working:
// Remove User
module.exports.removeUser = function(id, callback){
User.findById(id, function (err, doc) {
if (err) {
}
doc.remove(callback);
})
}
//Remove vouchers related to users
userSchema.pre('remove', function(next) {
this.model('Voucher').remove({ user: this._id }, next);
});
http://mongoosejs.com/docs/middleware.html
Please refer to the documentation. By design the middleware hook for remove is not fired for Model.remove, only for ModelDocument.remove function.
Anyone else searching for the same. deleteOne and deleteMany middlewares can be used with pre and post middlewares. Remove has been deprecated from the latest mongo release.
The code below got things working for me.
ModelSchema.pre("deleteOne", { document: true }, function(next) {
let id = this.getQuery()["_id"];
mongoose.model("Model_two").deleteMany({ property: id }, function(err, result) {
if (err) {
next(err);
} else {
next();
}
});
});
For anyone having the issue in future, check that you defined your hooks before calling mongoose.model().
Define Middleware Before Compiling Models
Calling pre() or post() after compiling a model does not work in Mongoose in general. For example, the below pre('save') middleware will not fire.
...
https://mongoosejs.com/docs/middleware.html#defining
You need to add
{document:false,query:true}
parameter to userSchema('remove',callback) middleware for Query.remove(). It is also important to understand difference between query middleware and document middleware.
Query middleware and Query.remove()
UserSchema.pre('remove',callback) will not be fired by default if it is a query middleware. As a solution
var User = module.exports = mongoose.model('User', userSchema);
//...
userSchema.pre('remove',{document:false,query:true}, callback);
//...
module.exports.removeUser = function(id, callback){
User.remove(callback);
}
We pass {document:false,query:true} parameter to middleware.
User.remove(callback)//Query.remove()
will not return a document from database.It will execute a remove operation and it will not call doc.remove(). It will call Query.remove().
Document middleware and doc.remove()
userSchema.pre('remove',callback) is registered as a document middleware by default.
The code snippet below will run smoothly without additional parameters.
var User = module.exports = mongoose.model('User', userSchema);
//...
userSchema.pre('remove', callback);
//...
module.exports.removeUser = function(id, callback){
User.find(query).remove(callback);//doc.remove()
}
userSchema.pre('remove',callback) will be fired because User.find(query) returns a mongoose document from database and User.find(query).remove(callback) will call doc.remove()
Only document middleware
// query document and delete
schema.findOne({ _id: id}).then((doc) => {
doc.remove().then(() => {
console.log(`it works!`)
})
})
// schema
schema.pre('remove', { document: true, query: false }, function() {
console.log('Removing doc!')
})
The delete was deprecated, try to use deleteOne instead.
Only query middleware. This will get called when you do Model.deleteOne() but not doc.remove().
// delete from model
schema.deleteOne({ _id: id }).then((removed) => {
console.log(`it works!`)
}
// schema
schema.pre('deleteOne', { query: true, document: false }, function() {
let id = this.getQuery()['_id']
console.log(`${id} was Removing!`)
})
Make sure you're using the exact functions that you're calling, eg pre('save') and pre('updateOne') do not get called for model.findOneAndUpdate()

Need to send response after forEach is done

I'm working with NodeJS + Mongoose and I'm trying to populate an array of objects and then send it to the client, but I can't do it, response is always empty because it is sent before forEach ends.
router.get('/', isAuthenticated, function(req, res) {
Order.find({ seller: req.session.passport.user }, function(err, orders) {
//handle error
var response = [];
orders.forEach(function(doc) {
doc.populate('customer', function(err, order) {
//handle error
response.push(order);
});
});
res.json(response);
});
});
Is there any way to send it after the loop has finished?
Basically, you could use any solution for async control flow management like async or promises (see laggingreflex's answer for details), but I would recommend you to use specialized Mongoose methods to populate the whole array in one MongoDB query.
The most straightforward solution is to use Query#populate method to get already populated documents:
Order.find({
seller: req.session.passport.user
}).populate('customer').exec(function(err, orders) {
//handle error
res.json(orders);
});
But if, for some reason, you can't use this method, you could call Model.populate method yourself to populate an array of already fetched docs:
Order.populate(orders, [{
path: 'customer'
}], function(err, populated) {
// ...
});
One solution is to use Promises.
var Promise = require('bluebird');
Promise.promisifyAll(Order);
router.get('/', isAuthenticated, function(req, res) {
Order.findAsync({ seller: req.session.passport.user })
.then(function(orders) {
return Promise.all(orders.map(function(doc){
return Promise.promisify(doc.populate).bind(doc)('customer');
}));
}).then(function(orders){
// You might also wanna convert them to JSON
orders = orders.map(function(doc){ return doc.toJSON() });
res.json(orders);
}).catch(function(err){
//handle error
});
});
BlueBird's .promisifyAll creates an …Async version of all functions of an object, which saves you an extra step in configuring the initial promise. So instead of Order.find I used Order.findAsync in above example

How can I write sails function on to use in Controller?

I have a question on sails js:
How can I write sails function on model To use in Controler? like:
beforeValidation / fn(values, cb)
beforeCreate / fn(values, cb)
afterCreate / fn(newlyInsertedRecord, cb)
If you are actually trying to use one of the lifecycle callbacks, the syntax would look something like this:
var uuid = require('uuid');
// api/models/MyUsers.js
module.exports = {
attributes: {
id: {
type: 'string',
primaryKey: true
}
},
beforeCreate: function(values, callback) {
// 'this' keyword points to the 'MyUsers' collection
// you can modify values that are saved to the database here
values.id = uuid.v4();
callback();
}
}
Otherwise, there are two types of methods you can create on a model:
instance methods
collection methods
Methods placed inside the attributes object will be "instance methods" (available on an instance of the model). i.e.:
// api/models/MyUsers.js
module.exports = {
attributes: {
id: {
type: 'string',
primaryKey: true
},
myInstanceMethod: function (callback) {
// 'this' keyword points to the instance of the model
callback();
}
}
}
this would be used as such:
MyUsers.findOneById(someId).exec(function (err, myUser) {
if (err) {
// handle error
return;
}
myUser.myInstanceMethod(function (err, result) {
if (err) {
// handle error
return;
}
// do something with `result`
});
}
Methods placed outside the attributes object but inside the model definition are "collection methods", i.e.:
// api/models/MyUsers.js
module.exports = {
attributes: {
id: {
type: 'string',
primaryKey: true
}
},
myCollectionMethod: function (callback) {
// 'this' keyword points to the 'MyUsers' collection
callback();
}
}
the collection method would be used like this:
MyUsers.myCollectionMethod(function (err, result) {
if (err) {
// handle error
return;
}
// do something with `result`
});
P.S. the comments about what the 'this' keyword will be are assuming that you use the methods in a normal way, i.e. calling them in the way that I described in my examples. If you call them in a different way (i.e. saving a reference to the method and calling the method via the reference), those comments may not be accurate.

Resources