I'm using a Sequelize for my node.js app. I use findOrCreate() method to create a new user if not exist. Accordingly to docs findOrCreate returns an array containing the object that was found or created and a boolean that will be true if a new object was created and false if not.
The sequelize recommend to use spread() method which divides the array into its 2 parts and passes them as arguments to the callback function. First part is a user object and second is boolean if new row was added.
I work in async/await style. My code is:
app.post('/signup', async (req, res) => {
try {
let insertedUser = await User.findOrCreate({
where: { email: req.body.userEmail },
defaults: {
pass: req.body.userPass
}
})
insertedUser.spread((user, created) => {
if(created) {
res.json(user.email)
}
})
} catch (err) {
console.log(`ERROR! => ${err.name}: ${err.message}`)
res.status(500).send(err.message)
}
}
})
After post request I get an error:
ERROR! => TypeError: insertedUser.spread is not a function
Why is that, the doc says it must be a function?
spread method does not exist on resolved value from findOrCreate. You have to either chain spread with then of promise returned by findOrCreate. Or you need to chain with findOrCreate directly and use await.
Here is the updated code with await:
app.post('/signup', async (req, res) => {
try {
let created = await User.findOrCreate({
where: { email: req.body.userEmail },
defaults: {
pass: req.body.userPass
}
}).spread((user, created) => {
return created;
})
if(created) {
res.json(user.email)
}
} catch (err) {
console.log(`ERROR! => ${err.name}: ${err.message}`)
res.status(500).send(err.message)
}
}
})
Related
Hi I am using sequelize ORM with Postgres database for this node.js express.js app. As for testing I am using mocha, chai and sinon.
I am trying to complete a test for a class's method. The class instant i call it userService and the method is findOneUser .. This method has got an argument id .. So in this moment I want to test for a throw error the test works if I dont put an argument. That means this test is obviously not complete.
Here is the class method I want to test
userService.js
module.exports = class UserService {
async findOneUser(id) {
try {
const user = await User.findOne({ where: { id: id } }); // if null is returned error is thrown
if (!user) {
throw createError(404, "User not found");
}
return user;
} catch (err) {
throw err
}
}
}
And here is my test code
userService.spec.js
describe.only("findOne() throws error", () => {
let userResult;
const error = customError(404, "User not found"); // customError is a function where I am throwing both status code and message
before("before hook last it block issue withArgs" , async () => {
// mockModels I have previously mocked all the models
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).resolves(null); // basically have this called and invoked from calling the method that it is inside of based on the argument fakeId
userResult = sinon.stub(userService, "findOneUser").throws(error); // 🤔this is the class instances method I wonder how to test it withArguments anything I try not working but SEE BELOW COMMENTS🤔
});
after(() => {
sinon.reset()
});
it("userService.findOneUser throws error works but without setting arguments 🤔", () => {
expect(userResult).to.throw(error);
});
/// this one below still not working
it("call User.findOne() with incorrect parameter,, STILL PROBLEM 🤯", () => {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
})
});
But for the method of my class findOneUser has an argument (id) how can I pass that argument into it where I am stubbing it?
Or even any ideas on how to fake call the class method?? I want both it blocks to work
EDIT
I forgot to mention I have stubbed the mockModels.User already and that was done before the describe block
const UserModel = {
findByPk: sinon.stub(),
findOne: sinon.stub(),
findAll: sinon.stub(),
create: sinon.stub(),
destroy: sinon.stub()
}
const mockModels = makeMockModels( { UserModel } );
// delete I am only renaming UserModel to User to type things quicker and easier
delete Object.assign(mockModels, {['User']: mockModels['UserModel'] })['UserModel']
const UserService = proxyquire(servicePath, {
"../models": mockModels
});
const userService = new UserService();
const fakeUser = { update: sinon.stub() }
SOLUTION
I think this might be the solution to my problem strange but it works the tests is working with this
describe.only("findOne() throws error", async () => {
const errors = customError(404, "User not found"); // correct throw
const errors1 = customError(404, "User not foundss"); // on purpose to see if the test fails if should.throw(errors1) is placed instead of should.throw(errors)
after(() => {
sinon.reset()
});
// made it work
it("call User.findOne() with incorrect parameter, and throws an error, works some how! 🤯", async () => {
userResult = sinon.spy(userService.findOneUser);
try {
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).threw(errors);
await userResult(fakeId);
} catch(e) {
// pass without having catch the test fails 😵💫
} finally {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
}
});
it("throws error user does not exist,,, WORKS", () => {
expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw
mockModels.User.findOne.withArgs(fakeId).should.throw(errors); // specially this part without having the catch test fails. but now test works even tested with errors1 variable
expect(userResult).to.throw;
});
});
MORE CLEANER SOLUTION BELOW
I like this solution more as I call the method inside within the describe block, then I do the test of the two it blocks.
The problem was I should not have stubbed the class method but should have called it directly, or used sinon.spy on the method and call it through the spy. As for checking that the errors are as expected then running this line of expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw(errors); was the solution I needed.
describe.only('findOne() user does not exists, most cleanest throw solution', () => {
after(() => {
sinon.reset()
});
const errors = customError(404, "User not found");
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).threw(errors);
userResult = sinon.spy(userService, "findOneUser"); // either invoke the through sinon.spy or invoke the method directly doesnt really matter
userResult(fakeId);
// userResult = userService.findOneUser(fakeId); // invoke the method directly or invoke through sinon.spy from above
it('call User.findOne() with invalid parameter is called', () => {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
})
it('test to throw the error', () => {
expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw(errors);
expect(userResult).to.throw;
})
});
I'm building out a quick API for an IOT type app. The structure is that I have a sensor document with latestValue1 and latestVoltage values. Each sensor document also has a collection of readings (an hourly log, let's say.)
The Set function works fine to update latestValue1 and latestVoltage, but I'm struggling to work out how to create the readings collection and add a document to it- the code below gives me TypeError: document.collection is not a function
app.put('/api/update/:item_id', (req, res) => {
(async () => {
try {
const document = db.collection('sensors').doc(req.params.item_id).set({
latestValue1: req.body.value1,
latestVoltage: req.body.voltage
}, {merge: true});
await document.collection('readings').add({
value1: req.body.value1,
voltage: req.body.voltage
});
return res.status(200).send();
} catch (error) {
console.log(error);
return res.status(500).send(error);
}
})();
});
How can I fix the code above to correctly add a new document to the readings collection?
set() doesn't return a DocumentReference object. It returns a promise which you should await.
await db.collection('sensors').doc(req.params.item_id).set({
latestValue1: req.body.value1,
latestVoltage: req.body.voltage
}, {merge: true});
If you want to build a reference to a subcollection, you should chain calls to get there. add() also returns a promise that you should await.
await db.collection('sensors').doc(req.params.item_id)collection('readings').add({
value1: req.body.value1,
voltage: req.body.voltage
});
FYI you can also declare the entire express handler function async to avoid the inner anonymous async function:
app.put('/api/update/:item_id', async (req, res) => {
// await in here
});
I'm using asynchronous virtual properties to count how often that document has been referenced in a specific other collection. This feature has been added.
// Schema mortician.js
const Pickup = require('./pickup')
const mongoose = require('mongoose')
const mortSchema = mongoose.Schema({
name: {
type: String,
required: true
}
}, { timestamps: true })
mortSchema.virtual('count').get( async function () {
return await Pickup.countDocuments({ mortician: this._id })
})
module.exports = mongoose.model('Mortician', mortSchema)
However, when I try to render it like this, it's returning a Promise: Promise { <pending> } and the displayed value is [object Promise], like joseym describes over here: Support for Asynchronous Virtual #1894
async index(req, res) {
try {
const morticians = await Mortician.find({}).exec()
res.render('pages/morticians', {
title: 'Bestatter',
page: req.originalUrl,
morticians: morticians
})
} catch (err) { err => console.log(err) }
..
}
Since I'm directly passing the morticians element to render, I've idea where to place the needed await for mortician.count. I want to avoid looping (for (const mortician of morticians)), before passing it to res.render. How to solve this?
Does it even make sense, to query ("OtherSchema".find..) with in an virtual property? What's best practice?
You need to execute your promise, then save the result in a variable which will be used to render it
something like this
async index(req, res) {
try {
const morticians = await Mortician.find({}).exec();
res.render('pages/morticians', {
title: 'Bestatter',
page: req.originalUrl,
morticians: morticians
})
} catch (err) {
err => console.log(err)
}
}
I have a function that looks up data in several different documents, add some of the date from each document to a object and returns the object with the combines data from the different documents. The only problem is that the object is returned before any of the transactions are completed. I have googled a lot, and the only solution i can find is on Stack Overflow, but from 2012.... There must be some "newer" better way to do it? Preferably without installing more npm stuff.
Here is my function
function getPreviousEventCard(event, callback) {
const card = {};
card.dateStart = event.dateStart;
card.dateEnd = event.dateEnd;
card.userID = event.userID;
card.adminID = event.adminID;
// 1st async call
Profile.getFullName(event.userID, (err, name) => {
if (err) return callback(err, null);
card.bsName = name.fullName;
});
// 2nd async call
Profile.getProfileByUserId(event.parentID, (err, profile) => {
if (err) return callback(err, null);
card.parentName = profile.fullName;
card.address = `${profile.address} ${profile.zip}`
});
// somehow wait until both(all) async calls are done and then:
return callback(null, card);
}
btw, 'Profile' is a mongoose schema, and the get methods are using the findOne() method.
I have tried to nest the functions and have the return callback as the most inner, but then it is never returned for some reason.
If you're using NodeJS v8 or higher, I would promisify those functions:
const getFullName = promisify(Profile.getFullName);
const getProfileByUserId = promisify(Profile.getProfileByUserId);
...and then use Promise.all:
function getPreviousEventCard(event, callback) {
Promise.all([
// 1st async call
getFullName(event.userID),
// 2nd async call
getProfileByUserId(event.parentID)
])
.then(([name, profile]) => {
const card = {
dateStart: event.dateStart,
dateEnd: event.dateEnd,
userID: event.userID,
adminID: event.adminID,
bsName: name.fullName,
parentName: profile.fullName,
address: `${profile.address} ${profile.zip}`;
};
callback(null, card);
})
.catch(err => callback(err);
}
or better yet, make getPreviousEventCard return a promise:
function getPreviousEventCard(event) {
return Promise.all([
// 1st async call
getFullName(event.userID),
// 2nd async call
getProfileByUserId(event.parentID)
])
.then(([name, profile]) => {
return {
dateStart: event.dateStart,
dateEnd: event.dateEnd,
userID: event.userID,
adminID: event.adminID,
bsName: name.fullName,
parentName: profile.fullName,
address: `${profile.address} ${profile.zip}`;
};
});
}
Preferably without installing more npm stuff
If you did want to install more npm stuff, there's an npm module that will promisify an entire API (rather than function by function) using a declarative description of the APIs functions: https://www.npmjs.com/package/promisify
I am using sequelize with MySQL. For example if I do:
models.People.update({OwnerId: peopleInfo.newuser},
{where: {id: peopleInfo.scenario.id}})
.then(function (result) {
response(result).code(200);
}).catch(function (err) {
request.server.log(['error'], err.stack);
).code(200);
});
I am not getting information back if the people model was succesfully updated or not. Variable result is just an array with one element, 0=1
How can I know for certain that the record was updated or not.
Here's what I think you're looking for.
db.connections.update({
user: data.username,
chatroomID: data.chatroomID
}, {
where: { socketID: socket.id },
returning: true,
plain: true
})
.then(function (result) {
console.log(result);
// result = [x] or [x, y]
// [x] if you're not using Postgres
// [x, y] if you are using Postgres
});
From Sequelize docs:
The promise returns an array with one or two elements. The first element x is always the number of affected rows, while the second element y is the actual affected rows (only supported in postgres with options.returning set to true.)
Assuming you are using Postgres, you can access the updated object with result[1].dataValues.
You must set returning: true option to tell Sequelize to return the object. And plain: true is just to return the object itself and not the other messy meta data that might not be useful.
You can just find the item and update its properties and then save it.
The save() results in a UPDATE query to the db
const job = await Job.findOne({where: {id, ownerId: req.user.id}});
if (!job) {
throw Error(`Job not updated. id: ${id}`);
}
job.name = input.name;
job.payload = input.payload;
await job.save();
On Postgres:
Executing (default): UPDATE "jobs" SET "payload"=$1,"updatedAt"=$2 WHERE "id" = $3
Update function of sequelize returns a number of affected rows (first parameter of result array).
You should call find to get updated row
models.People.update({OwnerId: peopleInfo.newuser},
{where: {id: peopleInfo.scenario.id}})
.then(() => {return models.People.findById(peopleInfo.scenario.id)})
.then((user) => response(user).code(200))
.catch((err) => {
request.server.log(['error'], err.stack);
});
Finally i got it. returning true wont work in mysql , we have to use findByPk in order hope this code will help.
return new Promise(function(resolve, reject) {
User.update({
subject: params.firstName, body: params.lastName, status: params.status
},{
returning:true,
where: {id:id }
}).then(function(){
let response = User.findById(params.userId);
resolve(response);
});
});
same thing you can do with async-await, especially to avoid nested Promises
You just need to create async function :)
const asyncFunction = async function(req, res) {
try {
//update
const updatePeople = await models.People.update({OwnerId: peopleInfo.newuser},
{where: {id: peopleInfo.scenario.id}})
if (!updatePeople) throw ('Error while Updating');
// fetch updated data
const returnUpdatedPerson = await models.People.findById(peopleInfo.scenario.id)
if(!returnUpdatedPerson) throw ('Error while Fetching Data');
res(user).code(200);
} catch (error) {
res.send(error)
}
}
There is another way - use findByPk static method and update not-static method together. For example:
let person = await models.People.findByPk(peopleInfo.scenario.id);
if (!person) {
// Here you can handle the case when a person is not found
// For example, I return a "Not Found" message and a 404 status code
}
person = await person.update({ OwnerId: peopleInfo.newuser });
response(person).code(200);
Note this code must be inside an asynchronous function.
You can fetch the model to update first, and call set() then save() on it. Returning this object will give you the updated model.
Although this might not be the shortest way to do it, I prefer it because you can handle not found and update errors separately.
const instance = await Model.findOne({
where: {
'id': objectId
}
});
if (instance && instance.dataValues) {
instance.set('name', objectName);
return await instance.save(); // promise rejection (primary key violation…) might be thrown here
} else {
throw new Error(`No Model was found for the id ${objectId}`);
}
If you're using postgres and updating one row.
try {
const result = await MODELNAME.update(req.body, {
where: { id: req.params.id },
returning: true
});
if (!result) HANDLEERROR()
const data = result[1][0].get();
res.status(200).json({ success: true, data });
} catch (error) {
HANDLEERROR()
}