Lodash _.merge function not overwriting properties with updated information - node.js

In my user edit route, I am trying to use the Lodash merge function to update the returned user document (from Mongoose) with the updates sent in req.body. Here is my code:
const { dobYear, dobMonth, dobDay } = req.body;
const dob = formatDob(dobYear, dobMonth, dobDay);
const user = await db.User.findById(req.params.id);
const updates = pick(req.body, [ 'fullName', 'email', 'password', 'gender', 'address', 'avatar_url' ]);
merge(user, [updates, dob]);
let updatedUser = await user.save();
The problem is even when I send an updated email in the request, the merge does not seem to actually overwrite the old email value with the new one (from updates).

You are trying to merge an object with an array in the way you are passing the arguments to merge.
See documentation. And I assume your confusion came from the fact that in the docs they have _.merge(object, [sources]). However if you see in the Arguments section they have:
[sources] (...Object): The source objects. Meaning a list of objects rather than an actual array.
Try this:
var user = { 'a': 1, 'b': 2 };
var updates = { 'a': 3, 'c': 4, 'email': 'aaa#bbb.com' };
let dob = '11-11-2019'
let result = _.merge(user, updates, {dob}); // <-- ...Object
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

merge(user, [updates, dob]);.
Incorrect usage. Accepts the following :
Arguments
object (Object): The destination object.
[sources] (...Object): The source objects.
Observe the..., that means it accepts a variable number of arguments.
merge(user, updates, { dob })

Related

Mongoose findOneAndUpdate() using current data

I am try to track the number of downloads per click on a website.
server.js
router.post("/download", async (req, res) => {
let id = req.body.id;
id = parseInt(id);
let doc = await db.findOneAndUpdate({_id: id}, {downloads: 100});
});
Note: This works
But I'm trying to increase the number by 1 each time.
For example: Let's say the current number of downloads is 5, how do I do it that if there's a post request. The number of downloads increases by 1.
const { body: { id } } = req;
const intCasetedId = parseInt(id);
const retrievedDocument = await db.findOneAndUpdate({ id }, { $inc: { downloads: 1 } });
A couple things are happening here.
First I get the id value from the the req argument using a destructuring assignment.
I use only const to ensure I do not mutate variable values.
I also use the object property value shorthand notation to skip '_id' key in the search query argument. Quoting mongoose documentation:
Issues a mongodb findAndModify update command by a document's _id field. findByIdAndUpdate(id, ...) is equivalent to findOneAndUpdate({ _id: id }, ...).
Then I am using '$inc' operator to increment the downloads field by 1.
I would also highly recommend for you to research eslint

How to splice an object from array of objects?

The thing is i am doing a array of favorites for each user object.
I did the insert request to the array to add some favorite recipe.
the problem is when i want to remove favorite from the array
its always remove the last object and not the exact object i want.
const recipe = await getRecipes(req.params.id); //gives the recipe object
let user = await User.findById(req.user._id); // gives the user object
console.log(recipe);
user.recipes.splice(user.recipes.indexOf(recipe), 1);
await user.save();
res.send(user);
The problem is that your call to indexOf passing the recipe object is not finding the element in the array so it returns -1. See how this code works:
let x = [{id: 1}, {id: 2}, {id: 3}]
let obj = {id: 2}
let i = x.indexOf(obj)
// i is -1 since obj isn't in the array.
// Another object that looks like obj is there,
// but they aren't the same exact object
console.log("i=",i)
// This will remove the last since splicing with -1 does that
x.splice( x.indexOf("d"), 1)
console.log(x)
// when the array has objects in it you can use `findIndex`
let y = [{id: 1}, {id: 2}, {id: 3}]
let j = y.findIndex(e => e.id === obj.id)
console.log("j=",j)
y.splice( j, 1 )
console.log(y)
So what you want to do is find a reliable way to find the index of the recipe in the array. See the 2nd example for how you can find the index within the object. Array.findIndex lets you compare objects in a way that's specific to the object structure.
If you want to remove the object form the array, you can use filter method like this:
user.recipes = user.recipes.filter(r => {
return r !== recipe;
})

Ignore multiple fields in unique validation rule in AdonisJs

I am trying to to update my user and applying Unique validator on email to prevent duplicates.
I need to ignore email uniqueness for provided user_id, and those records which are marked is_deleted to 1.
Only first statement works, if I place is_deleted,1 before id,${data.user_id} it works for deleted. but not for user_id.
get rules() {
const data = this.ctx.request.post()
console.log('current context: ', data.user_id)
return {
// email: `required|email|unique:users,email,id,${data.user_id},is_deleted,1`,
email: `required|email|unique:users,email,id,${data.user_id}|unique:users,email,is_deleted,1`,
phone_number: 'required|max:10',
status_id: 'required'
}
}
However, only first statement for ignore works, second one is not working
I would recommend extending the validation framework and add a custom rule (a good name would be unique_email). You will find it more productive and testable. The code would be similar to this:
const uniqueEmailFn = async (data, field, message, args, get) => {
const email = get(data, 'email')
const userId = get(data, 'user_id')
if (!email) {
/**
* skip validation if value is not defined. `required` rule
* should take care of it.
*/
return
}
const [table, column] = args
const row = await Database.table('users')
.where('id', userId)
.where('email', email)
.where('is_deleted', false)
.first()
if (row) {
throw 'The inputted e-mail is already taken'
}
}
Generally speaking, it's preferable to use the default rules for simple and generic validations, and business-specific ones can be added by extending the framework.

How do I use transactions or batches to read the value of an update, then perform more updates using that value?

What is the best approach to do a batch update or transaction, that reads a value of the first update, then uses this value to make further updates?
Here is an example:
//create person
const id = await db
.collection("person")
.add({ ...person })
.then(ref => ref.id)
//then do a series of updates
let batch = db.batch()
const private_doc = db
.collection("person")
.doc(id)
.collection("private")
.doc("data")
batch.set(private_doc, {
last_modified,
version: 1,
versions: []
})
const some_index = db.collection("data").doc("some_index")
batch.update(some_index, {
[id]: { first_name: person.first_name, last_name: person.last_name, last_modified }
})
const another_helpful_doc = db.collection("some_other_collection").doc("another_helpful_doc")
batch.update(another_helpful_doc, {
[id]: { first_name: person.first_name, last_name: person.last_name, image: person.image }
})
return batch.commit().then(() => {
person.id = id
return person
})
You can see here if there is an error any of the batch updates, the person doc will still be created - which is bad. I could add in a catch to delete the person doc if anything fails, however interested to see if this is possible with transactions or batches.
You can call the doc() method, without specifying any path, in order to create a DocumentReference with an auto-generated ID and, then, use the reference later. Note that the document corresponding to the DocumentReference is NOT created.
So, the following would do the trick, since all the writes/updates are included in the batched write:
const new_person_ref = db.collection("person").doc();
const id = new_person_ref.id;
let batch = db.batch()
batch.set(new_person_ref, { ...person })
const private_doc_ref = db // <- note the addition of ref to the variable name, it could help avoiding errors, as this is not a DocumentSnapshot but a DocumentReference.
.collection("person")
.doc(id)
.collection("private")
.doc("data")
batch.set(private_doc_ref, {
last_modified,
version: 1,
versions: []
})
//....

How to clone/copy instance item/row in sequelize

I tried to find a way to copy/clone instances in Sequelize but without success. Is there any way to do it with a built-in function or without? What I want is to simply copy rows in the database and the new item should have only a different id.
There is no such direct function for that , What you can do is :
Fetch the object that you want to clone/copy
Remove Primary Key from it
Make a new entry from it
model.findOne({ //<---------- 1
where : { id : 1 } ,
raw : true })
.then(data => {
delete data.id; //<---------- 2
model.create(data); //<---------- 3
})
As said, there is no such direct function for that (thanks Vivek)
If you find useful, place the following code on your model class:
async clone() {
let cData = await THISISMYMODEL.findOne({
where: { id: this.id},
raw: true,
});
delete cData.id;
return await THISISMYMODEL.create(data);
}
Take into account that "THISISMYMODEL" should be the model class defined and "id" the primary key attribute used.
Also take into account the use of Foreign Keys (relations with other models), it will use the same keys. Otherwise you should clone those instances too.
You may need to update the name though or some other field to identify it as a copy,
const data = await model.findOne({ where: {id: 1}, raw: true, attributes: { exclude: ['id'] } });
data.name = data.name + '(copy)';
const newRecord = await model.create(data);
Write a Model.create(data) function inside Model.js and call this function from inside of a loop, as many times you need it will create the copy of the same data.

Resources