ORM: Updating all models of a given instance - javascript

I'm having an conceptual issue on how to update all instances of a model once I have updated it.
Imagine the following method renameUser (could be any ORM):
async function renameUser(userId: number, newUsername: string) {
const user = await User.findByPk(userId);
await user.update({ username: newUsername });
}
And the following usage:
const user = await User.create({ username: "old" });
// user.username === "old"
this.renameUser(user.id, "new");
// still, user.username === "old"
Obviously this problem wouldn't exist if I would pass the user object directly into the update method, but that only works in this simple example - In my case it is actually not possible to share the same instance, since it can be modified via hooks in an entirely different context.
So, one simple solution would be to call user.reload() after the call, which will pull the latest user data from the database:
const user = await User.create({ username: "old" });
// user.username === "old"
this.renameUser(user.id, "new");
user.reload();
// now: user.username === "new"
However, this requires me to know that the renameUser method will change my user object. In this case it is obvious, but if the method is called that's not always possible.
Is there any pattern I can use to work around this? One thing which came in my mind was to create a UserFactory which ensures that I only have one instance of a user (indexed by its primary key) at any time, and then update that instance. But I was wondering how others solve it? Is it a common problem?

Why you dont use .save() in updateUser function?
const renameUser = async (userId, newUsername) => {
const user = await User.findByPk(userId);
user.username = newUsername;
await user.save();
return user;
}
and use it like this
const user = await User.create({ username: "old" });
// user.username === "old"
user = await this.renameUser(user.id, "new");
// now, user.username === "new"

You can run your queries more efficiently by just running an update using the Model.update() function instead of querying for an Instance and then updating it.
async function renameUser(userId: number, newUsername: string) {
const [ updatedRowCount, updateRowOnPostgresOnly ] = await User.update({
username: newUsername,
}, {
where: {
id: userId,
},
});
// you can use the updated row count to see if the user changed.
const isUpdated = updatedRowCount > 0;
return isUpdated;
}
Now you can await the result to see if the row changed, even without loading it.
const user = await User.create({ username: "old" });
// user.username === "old"
const isUpdated = await this.renameUser(user.id, "new");
if (isUpdated) {
user.reload();
// now: user.username === "new"
}
Note that in your example you are not using await on the async runameUser() function.
This varies from your question a bit, but if you are already working with Model instances then you can use Instance.changed() to get the changed fields.
const user = await User.create({ username: "old" });
user.username = "old";
const changed = user.changed(); // == [ "username" ]
if (changed.length) {
await user.save();
}
*Note that in that example it checks for changed.length but really Sequelize won't do anything if you await instance.save() and nothing is in instance.changed() - change awareness of save.

Related

mongoose instance methods are showing different results

i'm currently making a server with javascript, koa, and mongoose.
i made two different mongoose instance methods, which receives password from JSON body
and hashes it with bcrypt, saves it on a 'hashedPassword' on user.
Initially, i wrote the second setPassword function (which uses promise, 'then' methods.),
but when the user document saved, the 'hashedPassword' property didn't showed up.
so i tried with first one, it worked well and user document has saved with hashedPassword.
although these two instance methods looks similar, i wonder why these methods leads to different result.. please help
import mongoose from "mongoose";
import bcrypt from "bcrypt";
const UserSchema = mongoose.Schema({
username: String,
hashedPassword: String,
});
UserSchema.methods.setPassword = async function (password) {
const hash = await bcrypt.hash(password, 10);
this.hashedPassword = hash;
};
// UserSchema.methods.setPassword = async function (password) {
// bcrypt.hash(password, 10).then((hash) => {
// this.hashedPassword = hash;
// });
// };
// this code didn't save the hashed
const User = mongoose.model("user", UserSchema);
export default User;
user controller would be like this
const register = async (ctx) => {
const { username, password } = ctx.request.body;
try {
const user = new User({
username,
});
await user.setPassword(password);
await user.save();
catch(e) { ctx.throw(500,e) }
}
This is not a mongoose-specific issue, you need to revise your understanding of how promises work.
Adding await before bcrypt.hash in the commented-out code should make it work, but there is no reason for you here to prefer .then over await.
In the second example, setPassword only fires the hashing process and doesn't ask the caller to wait until the hashing is done, and then adds the hashed password to the document, while the first one does.
You can try this approach using schema built-in methods:
UserSchema.pre('save', async function (next) {
if (this.isModified('hashedPassword')) {
this.hashedPassword= await bcrypt.hash(this.hashedPassword, 10)
}
next()
})
Use this pre-save function inside the schema like that :
UserSchema.pre('save', async function (next) {
// Only if password was moddified
if (!this.isModified('hashedPassword')) return next();
// then Hash password
this.hashedPassword = await bcrypt.hash(this.hashedPassword, 10);
next();
});

is there a way to automatically update everything in a mongoose document?

For example I want to update a mongoose document in a put request, I have to do this:
app.put('/update', async(req,res) => {
try{
const product = await Product.findById(req.body.id)
product.name = req.body.name
product.price = req.body.price
procut.discount = req.body.discount
// etc...
await product.save()
res.json(product)
}catch(e){
res.json({message: "Error updating the product"})
}
})
I'm asking if there is another faster and developer friendly way of updating products instead of typing each of the document properties and equal them to the req.body.[property]?
You can try the following for object merging
Object.assign(product, req.body)
note: i haven't tried with mongoose collection
You can use updateMany or findOneAndUpdate model methods, but it is more advisable to use .save()
https://mongoosejs.com/docs/api.html#model_Model.updateMany
https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
If you want to .save() to look cleaner, you can do like this:
async updateEntity(payload) {
const keysToUpdate = Object.keys(payload)
if (keysToUpdate.length === 0) {
throw new Error('Update payload must not be empty!')
}
const entity = await entityModel.findOne({ _id: redirect })
keysToUpdate.forEach((key) => {
entity[key] = payload[key]
})
await entity.save()}

Mongoose: save() is not a function when using find() and atributing value to variable

This is the basic structure of the Schema I am working with using mongoose:
const User = {
uid: {
type: String
},
routes: {
type: Array
}
}
In my application there is a POST to /route, in which uid and a new route are provided as "body parameters". In order to add to the routes array, I wrote a code similar to this (the only diference is that I check if the route already exists):
var user = await User.find({uid: uid}) // user is found, as expected
user[0].routes.push(route //parameter)
user.save()
When a POST request is made, though, it throws an error:
TypeError: user.save is not a function
What am I doing wrong?
user in your code is an array of documents
so you'll have mongo documents inside that array
you can't do array.save, you've to do document.save
await user[0].save()
var user = await User.find({uid: uid}) // user is found, as expected
if (user && user.length) {
user[0].routes.push(route //parameter)
await user[0].save(); // save the 1st element of the object
}
if your query returns only 1 record better use https://mongoosejs.com/docs/api.html#model_Model.findOne
var user = await User.findOne({uid: uid}) // user is found, as expected
if (user) {
user.routes.push(route //parameter)
await user.save(); // save the 1st element of the object
}
if you need to find only one specific user you should use findOne function instead
User.findOne({uid: uid})
.then(
(user) => {
user[0].routes.push(route //parameter);
user.save();
},
(err) => {
console.error(err);
}
)
I think bulkSave() can be what you're looking for:
var user = await User.find({uid: uid}
enter code user[0].routes.push(route //parameter)
await User.bulkSave(user)

update method in adonisjs controller

How to update data in the database without creating a new one?
async update ({ params, request, response }) {
const product = await Product.find(params.id)
product.name = request.input('name')
product.descr = request.input('descr')
product.qtd = request.input('qtd')
product.sub_descr = request.input('sub_descr')
product.price = request.input('price')
product.save()
return response.redirect('/')
}
This code is creating a new instance, the product.update() method returns me an error
Adonisjs lucid supports automatic inserts & updates - meaning it intelligently updates or inserts a record based on the input.
According to the docs:
The save method persists the instance to the database, intelligently determining whether to create a new row or update the existing row.
However if you want to perform an update/bulk update you can always build a query as mentioned here.
But the problem you might face it
Bulk updates don’t execute model hooks.
Example for Insert/Update:
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.email = 'foo#bar.com'
// Insert
await user.save()
user.age = 22
// Update
await user.save()
Example for Bulk Update:
const User = use('App/Models/User')
await User
.query()
.where('username', 'virk')
.update({ role: 'admin' })

check if object in array of objects - javascript

I know i have to use some but for some reason i cant seem to get it right. i have a collection in my mongodb database of posts. each post has an array of objects named "likes" that references the users that liked this post. so in my backend i want to check if the user exists in the likes array of the post. if it does not exist then like the post, else return with an appropriate message on my react frontend. The code i will include always returns false from some so a user can like a post infinite times.
exports.postLike = async (req, res, next) => {
const postId = req.query.postId;
const userId = req.query.userId;
console.log('postId: ' + postId);
try{
const post = await Post.findById(postId).populate('creator').populate('likes');
const user = await User.findById(userId);
if (!post.likes.some(post => post._id === user._id)){
post.likes.push(user);
console.log('liked a post');
const result = await post.save();
res.status(200).json({ message: 'Post liked!', post: result });
} else {
console.log('Post already liked!');
res.status(200).json({ message: 'Post already liked!', post: post });
}
}catch (err) {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
}
};
i clearly haven't understood, yet, how some works so if you can help that would be great. also if you have any other solution that would be good in this case then please post it. i tried some random codes with indexOf and includes for checking but it didn't work either. i am not sure which is the right way to check if the user object is included in the "likes" array of objects. i would prefer not to write any function of my own to check this, i want to do it using an existing function/method provided by javascript.
Going to offer a different route here. You are fetching all the data including a join to the creator and likes just to add a like to the collection. This is a little wasteful and can be achieved by just doing an update and use $addToSet which will add the like if it does not exist.
You then just check nModified in the result to know if it was added or not. So you can have:
const result = await Post.updateOne(
{
id: 1
},
{
$addToSet: {
likes: {
userId: mongoose.Types.ObjectId(req.query.userId)
}
}
}
);
console.info(result.nModified === 1);
Alternatively, you can use some as follows using === to compare type and value:
posts.likes.some(like => like.userId.toString() === req.query.userId)
MongoDB.ObjectId is a wrapper around a primitve, just like Number or Boolean. And just like
new Boolean(true) === new Boolean(true)
will be false, your comparison will fail too. You have to take out the primitive for comparison:
post._id.valueOf() === user._id.valueOf()

Categories

Resources