Sequelize update - node.js

I'm trying to update a model in Sequelize using the following code:
exports.updateItem = function(item) {
return new Promise((fulfill, reject) => {
models.TimesheetItem.update(item,{where: {id: item.id}})
.then(fulfill)
.catch(console.dir);
});
};
Where item is the result of doing models.TimeSheetItem.find()
The call never executes the .then and instead passes an empty object to the .catch.
I've looked over the documentation and it seems that this is the way to update a row, but I can't get it to work. What am I doing wrong?
Thank you!

According to the documentation the update method takes two parameters - first one is values which will be used to perform the update, and second one is options - so in your case this is the where clause. If you want to perform an update on a single instance only, you can do it in two ways - use Model.update() method, which can update multiple instances of this model at once matching the where clause, or perform instance.update() in order to update only the single instance. First option will look like that:
let updateValues = { name: 'changed name' };
models.Model.update(updateValues, { where: { id: 1 } }).then((result) => {
// here your result is simply an array with number of affected rows
console.log(result);
// [ 1 ]
});
The first option is not very useful when you want to update only single instance. So that is why there is a possibility of performing update() on Sequelize model instance
let updateValues = { name: 'changed name' };
instance.update(updateValues).then((self) => {
// here self is your instance, but updated
});
In your case, if the item parameter is a Sequelize model instance (not a plain javascript JSON object), your update function could be like that
exports.updateItem = function(item){
return item.update(values).then((self) => {
return self;
}).catch(e => {
console.log(e);
});
};
However, if the item is not a sequelize model instance but only a plain object with values you want to update, it could be done in two ways - first is to use Model.update() (just like you did), or second one is to retrieve TimesheetItem with id = item.id and then perform instance.update() as shown above
exports.updateItem = function(item){
models.TimesheetItem.update(item, { where: { id: item.id } }).then((result) => {
// here result will be [ 1 ], if the id column is unique in your table
// the problem is that you can't return updated instance, you would have to retrieve it from database once again
return result;
}).catch(e => {
console.log(e);
});
};
Or the second option with returning instance and performing update on it
exports.updateItem = function(item) {
return models.TimesheetItem.findById(item.id).then((itemInstance) => {
return itemIstance.update(item).then((self) => {
return self;
});
}).catch(e => {
console.log(e);
});
}
The difference is that you do not need to create and return your own Promise - sequelize methods like update() return promises by themselves.

{ error: type "where" does not exist}
Just an update to the solution, Sequelize now gives the error above when you include the 'where' option(as shown below). So take it out and it should work perfectly.
exports.updateItem = function(item){
models.TimesheetItem.update(item, { id: item.id }).then((result) => {
// here result will be [ 1 ], if the id column is unique in your table
// the problem is that you can't return updated instance, you would have to retrieve it from database once again
return result;
}).catch(e => {
console.log(e);
});
};

Related

Pushed object in forEach is empty at the map look end [duplicate]

This question already has an answer here:
Returning value from async function node.js
(1 answer)
Closed 1 year ago.
I'm looking to push object from a forEarch, after getting data inside forEarch, the console.log show me correct value and object are properly pushed to the array.
But at the end of the forEach, if I try to access to the object pushed, there is no object inside the array
Code below
async function get_line_items(items) {
line_items2 = [];
await items.forEach((element, index) => {
const id = element.id;
if (id) {
await getPriceFromCartCheckout(id, (err, results) => {
if (err) {
console.log(err);
return;
}
if (results && !isEmpty(results)) {
// Add new item
line_items2.push({
name: element.name,
amount: results[0].price,
currency: "eur",
quantity: element.quantity,
})
}
console.log(line_items2) // => Here objects are properly added to the array on each loop
});
}
})
console.log(line_items2) // => Here the array is empty.
return line_items2;
}
const line_items = await get_line_items(req.body[1].items);
console.log( line_items); // => Here the array is empty.
module.exports = {
getPriceFromCartCheckout: async (id) => {
return await pool.query(`SELECT volume, price FROM products WHERE id = ?`, [
parseInt(id)
])
.then(iuidtest => {
return iuidtest;
});
},
};
Any help would be appreciate :)
Found the solution here :
NodeJS async MySQL call to connection pool returns no result
You should await getProductIdWeightPrice and not the loop itself. The loop does what it's supposed to do - looping the array, calling some external methods. What those methods do - the loop does not care.
In order to "pause" the loop, you need to await the async call to get the data. Do that and it will work :)

Handling Callbacks in Classes

I'm building classes to find and quickly operate actions on mongodb documents. This is the UserCursor class. (Not talking about MongoDB's cursor)
exports { UserCursor };
class UserCursor {
private __id: object;
constructor(query: { _id?: object, otherId?: number }) {
let { _id, otherId } = query; // Shortens the vars' name
if (!_id && !otherId) return; // Checks if 1 identifier is provided
if (_id) { // If a _id is provided
Users.findOne({ _id }, (err, user) => {
this.__id = user._id;
});
} else if (otherId) { // If a otherId is provided
Users.findOne({ otherId }, (err, user) => {
console.log(1); // Debug, you'll see later
this.__id = user._id;
});
}
}
// Returns this.__id (which should have been initialized in the constructor)
get _id() {
console.log(2)
return this.__id;
}
}
When run, the console returns
2
1
I think you got the problem: the mongo callback in the constructor gets on after _id operates. How could I manage that, since the constructor gets activated each time the class is used?
It's not entirely clear to me, what exactly you want to happen and how you use this class but I assume you want to instantiate it and then be able to get _id instantaneously. If it's not the case, you may still get some useful info from my answer. Feel free to provide more details, I will update it.
So mongodb operations are asynchronous, if you do
const cursor = new UserCursor(...)
console.log(cursor._id)
(I assume you want this), first all operations in this thread will run, including the call to get _id(), then the callback code will. The problem with such asynchronous things is that now to use this _id you will have to make all of your code asynchronous as well.
So you will need to store a Promise that resolves with _id from mongodb and make a method getId that returns this promise like this:
private __id: Promise<object>
constructor(...) {
// ...
if(_id) {
this.__id = new Promise((resolve, reject) => {
Users.findOne({ _id }, (err, user) => {
if(err) return reject(err)
resolve(user._id)
});
})
} else {
// Same here
}
}
public getId() {
return this.__id;
}
Then use it:
const cursor = new UserCursor(...)
cursor.getId().then(_id => {
// Do smth with _id
})
Or
async function doStuff() {
const cursor = new UserCursor()
const _id = await cursor.getId()
}
doStuff()
If you now do it inside some function, you'll also have to make that function async
Also you could leave a getter like you have now, that will return a promise, but I find it less readable than getId():
cursor._id.then(_id => { ... })
const _id = await cursor._id

Chaining multiple Promises, loop over array using map and create mongoose document?

I'm retrieving a list of people from the database using getPeople(). As soon as I receive them as res, I want to prepare them to be stored in my local mongodb if they do not exist. Sometimes there're duplicate entries (for one id) within res. My issues is that it's not waiting for Person.create(pers) to finish, continues searching if this id is already in mongodb, can't find any since Person.create(pers) is still creating it and starts the second Person.create(pers)..
this.getPeople()
.then(res => {
return Promise.all(res.map(pers => {
pers.birthday = df(pers.birthday, 'dd.mm.yyyy')
pers.pickedUp = false
console.log(pers.id)
return Person
.find({ id: pers.id })
.exec()
.then(found => {
if (found === undefined || found.length == 0)
return pers
})
.then(pers => {
return Person
.create(pers)
.then(console.log('created'))
.catch(err => console.log(err))
})
}))
}).catch(err => console.log(err))
I expected the console output to be like this:
940191
created
940191
created
Instead, I'm getting this:
940191
940191
created
created
That's because Promise.all simply awaits all the promises you're mapping. It does not guarantee any order in which the promises are resolved.
If you want to sequentially process the elements of your res-array, you can simply use a for .. of loop in combination with async/await (note that this still needs some error handling, but it should give you something to start with):
async function getPeopleAndCreateIfNotExisting() {
const persons = [];
const res = await this.getPeople();
for (const pers of res) {
pers.birthday = df(pers.birthday, 'dd.mm.yyyy');
pers.pickedUp = false;
console.log(pers.id)
const found = await Person
.find({ id: pers.id }).exec();
if (found) {
persons.push(pers);
} else {
persons.push(await Person.create(pers));
}
}
return person;
}

Sequelize reload() wiping out all model data

I'm trying to reload a model in order to include association. This is my code:
const order = new Order({ total: 0 })
return order.save().then(savedOrder => {
const orderItems = req.body.items.map(i => {
return Eliquid.findByPk(i.eliquid).then(eliquid => {
const item = Item.build({
qty: i.qty,
n: i.n,
})
item.setEliquid(eliquid)
item.setOrder(savedOrder)
return item
.save().then(i => {
console.log('Saved, has id, has qty', i.id, i.qty)
return i.reload()
})
.then(i => {
console.log('Reloaded, qty is now NULL!', i.id, i.qty)
return i
})
})
})
Why after reloading my instance it gets whiped out? Don't know what I'm doing wrong
As far as I can tell, .reload() (unlike .save()) doesn't deliver the item back to the client; instead, it synchronises the original item's properties with those on the server.
I'm not sure you actually need to reload in this case because client & server representations of the item will automatically be in sync immediately after .save(), but here's an example of how you would exploit javascript's "closure" to maintain access to the item, after .reload().
const order = new Order({ 'total': 0 });
return order.save()
.then(savedOrder => {
const orderItems = req.body.items.map(i => {
return Eliquid.findByPk(i.eliquid)
.then(eliquid => {
const item = Item.build({ 'qty': i.qty, 'n': i.n });
item.setEliquid(eliquid);
item.setOrder(savedOrder);
return item.save();
})
.then(ii => {
console.log('Saved, has id, has qty', ii.id, ii.qty);
return ii.reload()
.then(() => { // .reload() does not repeat item back to client
console.log('same ii as before', ii.id, ii.qty); // here, access ii through closure.
return ii;
});
});
});
});
Notes:
for clarity ii is used in the second .then() callback to avoid reusing the .map() callback's symbol i.
the inner promise chain is flattened as far as possible. If you needed access to eliquid in the second inner .then(), you would need to revert to return item.save().then() and incur a futher level of nesting.
in addition to exploiting closure, other approaches exit - see the comprehensive answers here.

sharing a transaction or task object between multiple queries across multiple controllers in pg-promise

I am relatively new to node.js, postgresql, promises and infact stackoverflow so apologies in advance if things sound a little disjointed!
I am currently trying to run multiple queries within chained promises spread across various controllers. I want to run all the queries within the same transaction or task to eliminate multiple connects and disconnects to the database.
I have tried the following where I am adding a student and assigning two mentors for that student. The HTTP request is routed to the student controller which adds a student via the student repository. The student repository is where the task starts and is returned to the controller which forwards it to the mentor controller and along the chain it goes...
# HttpPost("/api/students/create")
addStudent( # Req()request) {
var studentid;
var mentorids= [];
//Add the student
return this.studentRepository.addStudent(request.body.student)
.then(newStudentId => {
studentid = newStudentId;
//Add the mentors, passing the transaction object received back from
studentRepository
return this.mentorController.addMentor(request.body.schoolid, request.body.mentors, newStudentId.transaction)
.then(result => {
var data = [];
console.log(result);
for (var role in result) {
data.push({
mentorid: result[role].roleid,
studentid: studentid
});
}
//Assigns the student to mentors in the link table
return this.studentRepository.assignMentor(data)
.then(result => {
return result;
})
})
});
}
Student repository
addStudent(student): any {
return this.collection.task(t => {
return this.collection.one(this.sql.addStudent, student)
.then(studentid => {
return {
studentid: studentid.studentid,
transaction: t
}
});
})
}
Mentor controller
addMentor(institutionid: number, mentors, t): any {
var promises = [];
var mentorIds = [];
for (var role in mentors) {
promises.push(this.roleController.registerRole(institutionid,mentors[role].role,t));
}
return t.batch(promises)
.then(result => {
return Promise.resolve(result);
})
}
Role controller
# HttpPost("/api/roles/register")
registerRole(institutionid, # Req()request, t ? ) : any {
console.log(request);
return this.roleRepository.checkRoleEnrollment(institutionid, request.email, request.roletype, t)
.then(result => {
return this.roleRepository.addRoleEnrollment(institutionid, request, t)
.then(data => {
return this.roleRepository.updateRoleEnrollment(data.roleenrollmentid, data.roleid)
.then(d => {
return data;
})
})
})
.catch (error => {
return Promise.reject(error);
});
}
I am getting the following error when I call checkEnrollment in the Role Controller:
"name": "Error",
"message": "Unexpected call outside of task.",
"stack": "Error: Unexpected call outside of task. at Task.query
(\api\node_modules\pg-promise\lib\task.js:118:19)
at Task.obj.oneOrNone (\api\node_modules\pg-promise\lib\database.js:491:31)
at RoleRepository.checkRoleEnrollment....
Any help would be much appreciated. Thanking you in advance.
As per my earlier comment:
That error means you are trying to access connection t allocated by a task somewhere outside of the task's callback function, i.e. the task's callback has returned, the connection was released, and then you are using the connection object allocated by the task from somewhere else, which is, of course, invalid.
b.t.w. I'm the author of pg-promise ;)
Below is what your code effectively doing, in a simplified form:
var cnReference;
db.task(t => {
cnReference = t;
// can only use `t` connection while executing the callback
})
.then(data => {
// we are now outside of the task;
// the task's connection has been closed already,
// and we can do nothing with it anymore!
return cnReference.query('SELECT...');
})
.catch(error => {
// ERROR: Unexpected call outside of task.
// We cannot use a task connection object outside of the task's callback!
});
You need to correct the implementation to make sure this doesn't happen.

Resources