How to update document with dynamic fields in node.js - node.js

Assume that I have a document in database which has the following structure;
id: 1,
name: alex,
age: 21
There are some updates in client side and the same document with id:1 returns as;
id: 1,
name: alex,
age: 21,
location: usa
where a new field location is added.
I want to update the document in database. I want to add this new inserted field but I couldn't find out how can I handle this situation. Because location is not static, it can be surname, job and so on. So new inserted fields are dynamic and I don't know in advance.
If the field location was static, I could write a query as;
db.collection("user").updateOne( {id: 1},
{$set: {name: "alex", age: "21", location: "usa"}}, function(err, result){
if(err){}
else{}
});
But now, how can I update the document with this newly added fields?

If you are saying that you have an updated object:
id: 1,
name: alex,
age: 21,
location: usa
then I assume that you have some object that has a value of:
{
id: 1,
name: 'alex',
age: 21,
location: 'usa'
}
e.g. as a result of parsing this JSON sent from the client:
{
"id": 1,
"name": "alex",
"age": 21,
"location": "usa"
}
If you have that object in a variable called object then all you have to do is:
db.collection("user")
.updateOne({id: object.id}, {$set: object}, function (err, result) {
if (err) {
// handle success
} else {
// handle success
}
});
But most likely you will need to use _id instead of id:
db.collection("user")
.updateOne({_id: object._id}, {$set: object}, function (err, result) {
if (err) {
// handle success
} else {
// handle success
}
});
If you want to be able to remove properties as well then maybe even:
db.collection("user")
.replaceOne({_id: object._id}, object, function (err, result) {
if (err) {
// handle success
} else {
// handle success
}
});
I wouldn't recommend putting data into the database without at least some sort of validation but it's possible to do what you're asking about in the way shown above.
For more info see:
Update Specific Fields
Replace a Document
in the Introduction to MongoDB in Node.js.

Related

MongoDB updateOne({$pull}) is matching a document, but not removing it from an array

I am building a Pokemon API MERN based app where users can search Pokemon from a search component and add them to their profile. The User schema contains an array of Pokemon, where each Pokemon is represented by a document containing the Pokedex number, the name of the Pokemon, and an array of sprite source links so that each Pokemon can be mapped to the user's profile. I am using the $push operator to add each Pokemon from a search result to the user's team from a put request, which works fine. Just like in the actual game, each user is limited to having 6 Pokemon on their team at one time, but a user's team can also contain duplicates of the same Pokemon, so in order to remove a Pokemon from a user's team I send a put request of the username of the user currently logged in as well as the _id of the Pokemon to be deleted. When I try this request, it returns an error saying that n:1 has been matched, but the document is not pulled from the user's array of Pokemon.
router.put("/del", auth, async (req, res) => {
// Find user
User.findOne({ username: req.body.username }).then((user) => {
// Try updating Pokemon array
const offTeam = user
.updateOne({ $pull: { pokemon: { _id: req.body.id } } }, function(
err,
data
) {
console.log("ERROR:", err, data, err.message);
})
.then((offTeam) => {
res.status(200).json(offTeam);
})
.catch((e) => {
return res.status(400).json({ msg: e.message });
});
});
});
Below is the error that is returned:
ERROR: null {
n: 1,
nModified: 0,
opTime: {
ts: Timestamp { _bsontype: 'Timestamp', low_: 2, high_: 1605810353 },
t: 4
},
electionId: 7fffffff0000000000000004,
ok: 1,
'$clusterTime': {
clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 2, high_: 1605810353 },
signature: { hash: [Binary], keyId: [Long] }
},
operationTime: Timestamp { _bsontype: 'Timestamp', low_: 2, high_: 1605810353 }
}
EDIT: Link to the repo, currently working in the delete branch.
https://github.com/nrhill1/pokeMERN/tree/delete
EDIT: Here's an example of my profile. I am trying to delete Marill and Houndoom, but neither will pull.
_id
:5fb5ef14e664d5172ee5e1ed
username
:"nic"
email
:"nic#gmail.com"
password
:"$2a$10$kKfpdkmPMeWAS.22nulCSOkVeqKPGxiA5PL9D5WhPsU0pAznq/J2u"
pokemon
:
Array
0
:
Object
_id
:5fb5ef26e664d5172ee5e1ee
id
:229
name
:"houndoom"
sprites
:
Array
1
:
Object
_id
:5fb614318558521aac650a4c
id
:183
name
:"marill"
sprites
:
Array
0
:"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokem..."
1
:"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokem..."
reg_date
:2020-11-19T04:05:40.436+00:00
__v
:0
You can use $in and set the ids you want to pull into an array like this (I think this is the easiest way for the future if you want to add more elements):
db.collection.update({
"_id": "5fb5ef14e664d5172ee5e1ed",
},
{
"$pull": {
"pokemon": {
"id": {
"$in": [
229,
183
]
}
}
}
},
{
"multi": true
})
The query first match by _id (user id I guess, but you can use username, email or other field if you want) and then pull all pokemons where id is inside the array. In this case 229 and 183 will be deleted.
Example here
Note that multi:true is not necessary if you use the method updateMany().
Using mongoose is the same query but using updateMany():
var idToUpdate = mongoose.Types.ObjectId(req.body.id)
var pokemonsToDelete = [229, 183]
var updated = await model.updateMany({
"_id": idToUpdate,
},
{
"$pull": {
"pokemon": {
"id": {
"$in": pokemonsToDelete
}
}
}
})

DynamoDB: Dynamoose update ($PUT default behaviour) not working as expected?

I'm trying to implement a PUT (update) API endpoint, passing the primary key of an element as a parameter in the path and the attributes I want to update as parameter in the body. But is not working as expected, instead of updating the attributes of an existing element, is creating a new element in the database with wrong attributes.
As far as I understand from the Dynamoose API documentation, Model.update(key, update, options, callback) updates and existing item in the table. For example, if we have a Dog model where age is one of the attributes, then this code
Dog.update({ownerId: 4, name: 'Odie'}, {age: 1}, function (err) {
if(err) { return console.log(err); }
console.log('Just a puppy');
})
would update the age of a Dog called 'Odie' with ownerId: 4
Now, I tried to do a similar update for my own API. I have a data model called InvoiceConfig where the primary key corresponds to a unique name/id (string) attribute and contains another attribute called providerVariants (an array of objects)
This is my API definition in swagger:
put:
description: Updates the invoice config matching the id (providerName)
operationId: updateInvoiceConfigByName
consumes:
- application/json
produces:
- application/json
parameters:
- name: id
in: path
required: true
type: string
- name: providerVariants
description: array of JSON objects
in: body
required: true
schema:
$ref: "#/definitions/ProviderVariantsDataList"
responses:
"200":
description: A confirmation message
schema:
$ref: "#/definitions/ResponseMessage"
"500":
description: Error message
schema:
$ref: "#/definitions/ErrorResponse"
And this is the function that implements the dynamoose update in my code:
updateInvoiceConfigByName: function(req, res) {
var name = req.swagger.params.id.value;
var providerVariants = req.body;
console.log("UPDATE /invoice-config");
InvoiceConfig.update({provideName: name}, providerVariants, function(err) {
if (err) {
console.log("Update InvoiceConfig error");
throw err;
}
res.status(200).send({
providerName: `${name}`,
message: 'provider invoice cfg updated'
});
});
}
I have an object in the database:
{
"providerName": "ID01",
"providerVariants": [
{
"displayName": "My 1st Provider ",
"phone": "915698471",
"availableTemplates": [
"default"
]
}
]
}
and I try to update it from swagger-ui passing the following paramenters:
ID01 in the endpoint path itself and a modified providerVariants array in the body:
[
{
"displayName": "My new name",
"phone": "913333333",
"availableTemplates": [
"default"
]
}
]
But as I said at the beginning, if I check the contents of my table, I see that the item with providerName "ID01" has not changed, and there is a new item created:
{
"providerName": {
"S": "[object Object]"
}
}
I suspect that in this new object the providerName (primary key) was populated with the providerVariants array, which is totally wrong. Any hints about how to fix this are welcome, I don't know how to proceed with the update. Other endpoints (get, delete, post) in my API are working fine, but I'm blocked with the update/put
There is a typo in your update.
InvoiceConfig.update({provideName: name}, providerVariants, function(err)
You're missing the 'r' in providerName :)

Combining 2 queries into 1 query

Let's assume I have a structure as;
{
name: "",
surname: "",
id: 1,
children: [
{id: 2, name: "", age: ""},
{id: 3, name: "", age: ""}
]
}
I want to add new children or update the existing children fields according to situation.
To add new children into the list, I wrote query which is;
db.collection.update({id: 1}, {
$push: {
children: {
name: "alex",
age: 12,
id : shortid.generate()
}
}
}, {
upsert:true
},function(err, result) {
}
Whether it is a insert or update, I should update the document with id 1. So I think about combining these 2 situations into 1 query.
If I want to add children to the children array I should use $push, otherwise if it is an update I should update the fields of children object. So by using $cond I wrote a query like this;
db.collection.update({id: 1},
{
$cond : {
if: {id: {$exists: 21}} ,
then: {
$set: {
children: { name: "new name", age: "new age"}
}
},
else: {
$push: {
children: {
name: "alex",
age: 12,
id : 21
}
}
}
}, {
upsert:true
},function(err, result){}
And I see it is not possible to have such query, I got an error. Was it too unrealistic? Should I take this two situation separately? I think it was a good idea because I update the same document with id:1 whether it's a update or insert. The only change is deciding whether I will set the fields or create and push them into the children array. What do you suggest for this situation? And yes, I admit, I'm trying to get some experience with mongodb and $cond.
Your first solution is correct and does what you want it to do. The upsert option tells mongo to check if the document exists or not. Mongo will then either insert the doc if it's not there or update an existing doc with the new values it a doc with the given id exists. You need not to do anything else.

I want a specific document and only the latest item in a subdocument (Mongodb / Node.js)

I have a model like this:
User:
{
name: "test",
phone: "1234567890",
password: "test",
email: "test#tester.com",
tasks: {
{ task: "do this", timestamp: "2015-08-01" },
{ task: "then this", timestamp: "2015-08-05" },
{ task: "finally this", timestamp: "2015-08-07" }
}
},
... (more users) ...
How can I get a specific user's details like name, email, and only 1 task the latest one?
User.find({phone: '1234567890', password: 'test'}, '_id name email tasks', function(err, user) {
if (err)
res.json({result: false});
res.json({result: !(user == null), object: user});
});
This returns all the tasks.
You could use $slice (projection)
User.find({
phone: '1234567890',
password: 'test'
}, {
name: 1,
email: 1,
tasks: {$slice: -1}
}, function (err, user) {
if (err) {
res.json({result: false});
return;
}
return res.json({result: !(user == null), object: user});
});
Note that tasks should be Array but not Object.
if you are inserting new task by push method of array. then you can get last record by getting length of tasks array and access last record by length - 1.
or you can refer this :
return document with latest subdocument only in mongodb aggregate

Update multiple elements with different value in Mongoose

I have document containing lists. Lets say they are:
[
{
_id: 52b37432b2395e1807000008,
name: ListA,
order: 1,
desc: 'More about List A'
},
{
_id: 52b37432b2395e1807000009,
name: LISTB,
order: 2,
desc: 'More about List B'
},
{
_id: 52b37432b2395e180700000e,
name: LISTC,
order: 3,
desc: 'More about List C'
},
{
..
}
]
Now I want to change their order field using a batch update. I have a JSON of updated_stage order
var updated_stage = [{_id: '52b37432b2395e1807000008', order:2},{_id: '52b37432b2395e180700000e', order:1}, {_id: '52b37432b2395e1807000009', order:3}]
Now I need to update LIST Model in Mongoose with the new array that I have. I know that I can update multiple documents with same value using Batch Update
Model.update({ }, { $set: { order: 10 }}, { multi: true }, callback);
But I have to update them by different values. How should I do it? Whats the most efficient way?
The most efficient way I could think of is to run a forEach loop over your updated_stage array.
Now take the _id and update order in the existing document in MongoDB.
Here my test with collection.forEach then call doc.save:
I use sync.each to know when all documents is save
var mongoose = require('mongoose'), async = require('async');
mongoose.connect('localhost', 'learn-mongoose');
var User = mongoose.model('User', {name: String});
async.series([
function (done) {
// remove User collection if exist
User.remove(done);
},
function(done) {
// re-create a collection with 2 users 'Mr One', 'Mr Two'
User.create([{name: 'Mr One'}, {name: 'Mr Two'}], done);
},
function(done) {
// upperCase user.name
User.find(function(err, users) {
async.each(users, function(user, callback) {
user.name = user.name.toUpperCase();
user.save(callback);
}, done); // done is call when all users are save!!!!
});
},
function(done) {
// print result
User.find(function(err, users) {
console.log(users);
done();
});
},
], function allTaskCompleted() {
console.log('done');
mongoose.disconnect();
});
You guys can use mongoose/mongodb bulkwrite function.
Reference

Resources