Mongoose: Add more items to existing object - javascript

Using Mongoose, How can I add more items to an object without replacing existing ones?
User.findOneAndUpdate(
{ userId: 0 },
{ userObjects: { newItem: value } }
);
The problem with above code is that it clears whatever was there before and replaces it with newItem when I wanted it just to add another item to userObjects(Like push function for javascript arrays).

Use dot notation to specify the field to update/add particular fields in an embedded document.
User.findOneAndUpdate(
{ userId: 0 },
{ "userObjects.newerItem": newervalue } }
);
or
User.findOneAndUpdate(
{ userId: 0 },
{ "$set":{"userObjects.newerItem": newervalue } }
);
or Use $mergeObjects aggregation operator to update the existing obj by passing new objects
User.findOneAndUpdate(
{"userId":0},
[{"$set":{
"userObjects":{
"$mergeObjects":[
"$userObjects",
{"newerItem":"newervalue","newestItem":"newestvalue"}
]
}
}}]
)

According to your question, i am guessing userObjects is an array.
You can try $push to insert items into the array.
User.findOneAndUpdate(
{ userId: 0 },
{ $push : {"userObjects": { newItem: value } }},
{safe :true , upsert : true},function(err,model)
{
...
});
For more info, read MongoDB $push reference.
Hope it helps you. If you had provided the schema, i could have helped better.

Just create new collection called UserObjects and do something like this.
UserObject.Insert({ userId: 0, newItem: value }, function(err,newObject){
});
Whenever you want to get these user objects from a user then you can do it using monogoose's query population to populate parent objects with related data in other collections. If not, then your best bet is to just make the userObjects an array.

Related

Is there a way to update an object in an array of a document by query in Mongoose?

I have got a data structure:
{
field: 1,
field: 3,
field: [
{ _id: xxx , subfield: 1 },
{ _id: xxx , subfield: 1 },
]
}
I need to update a certain element in the array.
So far I can only do that by pulling out old object and pushing in a new one, but it changes the file order.
My implementation:
const product = await ProductModel.findOne({ _id: productID });
const price = product.prices.find( (price: any) => price._id == id );
if(!price) {
throw {
type: 'ProductPriceError',
code: 404,
message: `Coundn't find price with provided ID: ${id}`,
success: false,
}
}
product.prices.pull({ _id: id })
product.prices.push(Object.assign(price, payload))
await product.save()
and I wonder if there is any atomic way to implement that. Because this approach doesn't seem to be secured.
Yes, you can update a particular object in the array if you can find it.
Have a look at the positional '$' operator here.
Your current implementation using mongoose will then be somewhat like this:
await ProductModel.updateOne(
{ _id: productID, 'prices._id': id },//Finding Product with the particular price
{ $set: { 'prices.$.subField': subFieldValue } },
);
Notice the '$' symbol in prices.$.subField. MongoDB is smart enough to only update the element at the index which was found by the query.

How to Query a nested child from firebase angular?

// my db structure now
rcv : {
visible: 'all',
ids: [
[0] : userId,
[1] : user2Id ]
}
this is how i query to get the data it works.
//service.ts
getAlbumByUserId(userId) {
return this.afs.collection('albums', ref => ref.where('rcv.visible', '==', 'all').where('rcv.ids', 'array-contains', userId)).valueChanges();
}
//component.ts
this.service.getAlbumByUserId(this.userId);
but i want to set the structure like this but i don't know how to query nested objects in firebase
// database structure
rcv : {
visible: 'all',
ids: {
userId: {
id: userId
}
user2Id: {
id: user2Id
}
}
}
You're looking for the array-contains operator, which can check if a field that is an array contains a certain value.
You're already using the correct array-contains operator, but not with the correct syntax. The array-contains operator checks whether any element of your array is exactly the same as the value you pass in. So you need to pass in the complete value that exists in the array:
ref.where('rcv.visible', '==', 'all').where('rcv.ids', 'array-contains', { id: userId })
As you add more data to the array, it may become unfeasible to reproduce the entire array element for the query. In that case, the common approach is to add an additional field where you keep just the IDs.
So you'd end up with one field (say rcv.users) where you keep all details about the receiving users, and one field (say rcv.ids) where you just keep their IDs, and that you use for querying.

How to update MongoDB document based on its previous values?

Sorry if this is pretty basic, but I'm a mongodb newbie and haven't been able to find an answer to this:
Let's say I'm doing the following:
db.collection("bugs").updateOne({ _id: searchId }, { $set: { "fixed": true }}
How to set "fixed" to the contrary of whatever the last value of "fixed" was? Without any additional queries? Something like { $set: { "fixed": !fixed }}
It is not really possible to achieve this in MongoDB as of now in just one operation by sticking to the idea of storing boolean values (might be possible in future versions of MongoDB). But, there is a workaround to do this by storing bits (0 or 1) to represent true or false instead of boolean values and performing bitwise xor operation on those in MongoDB as follows:
db.collection("bugs").updateOne(
{
_id: searchId
},
{
$bit : {
fixed: {
xor: NumberInt(1)
}
}
}
)
Please note that you also have to store 0 as NumberInt(0) to represent false and 1 as NumberInt(1) to represent true in the fixed prop as MongoDB by default treats all numbers as floating-point values.
This is not possible in MongoDB. You have to retrieve the doc from the db and then update it:
var doc = db.collection("bugs").findOne({ _id: searchId });
db.collection("bugs").updateOne({ _id: searchId }, { $set: { "fixed": !doc.fixed } }
Yes, it's possible to do that with MongoDB, of course !! ... just use the find's forEach feature like this:
db.collection("bugs").find({ _id: searchId }).forEach(function(bugDoc) {
db.collection("bugs").updateOne({ _id: searchId }, { $set: { "fixed": !bugDoc.fixed }});
});
NOTE: bugDoc contains all the fields of the original document and you can make all the calculations and changes you want in this double operation update

Unable to push data into current object inside mongo database

I am using mongodb native and Node.js 6.5.0.
I have user object in mongodb which is structured as:
{
"_id" : ObjectId("57d7d294d96a73d128c46db9"),
"id" : "105862592064",
"labels" : [
]
}
I have a loop (for each user found) get data from API and then push it into object attribute of array type. Where user id user.id and data to push is resp.labels.
This is my code:
db.collection('users').update(
{"id":user.id},
{"$push":{"users.labels":resp.labels}}
)
It doesn't return any error, neither it does update objects. What have I done wrong?
Try this:
db.collection('users').update(
{"id":user.id},,
{
$push: {
labels: {
$each: //yourArray
}
}
}
);
$push is for pushing a single element to an array. Use $push together with $each to push multiple elements. Also, quotes around the object labels should not be neccessary:
db.collection('users').update(
{ id:user.id },
{ $push: { labels: { $each: resp.labels } } }
)
Try $set
db.collection('users').update(
{"id":user.id},
{$set:{"users.labels":"hola"}})
Try to include {upsert:true}:
db.collection('users').update(
{"id":user.id},
{"$push":{"users.labels":resp.labels}},
{"upsert": true}
);
Upsert inserts new value if it doesn't already exists.

Mongoose find/update subdocument

I have the following schemas for the document Folder:
var permissionSchema = new Schema({
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
});
var folderSchema = new Schema({
name: { type: string },
permissions: [ permissionSchema ]
});
So, for each Page I can have many permissions. In my CMS there's a panel where I list all the folders and their permissions. The admin can edit a single permission and save it.
I could easily save the whole Folder document with its permissions array, where only one permission was modified. But I don't want to save all the document (the real schema has much more fields) so I did this:
savePermission: function (folderId, permission, callback) {
Folder.findOne({ _id: folderId }, function (err, data) {
var perm = _.findWhere(data.permissions, { _id: permission._id });
_.extend(perm, permission);
data.markModified("permissions");
data.save(callback);
});
}
but the problem is that perm is always undefined! I tried to "statically" fetch the permission in this way:
var perm = data.permissions[0];
and it works great, so the problem is that Underscore library is not able to query the permissions array. So I guess that there's a better (and workgin) way to get the subdocument of a fetched document.
Any idea?
P.S.: I solved checking each item in the data.permission array using a "for" loop and checking data.permissions[i]._id == permission._id but I'd like a smarter solution, I know there's one!
So as you note, the default in mongoose is that when you "embed" data in an array like this you get an _id value for each array entry as part of it's own sub-document properties. You can actually use this value in order to determine the index of the item which you intend to update. The MongoDB way of doing this is the positional $ operator variable, which holds the "matched" position in the array:
Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{
"$set": {
"permissions.$": permission
}
},
function(err,doc) {
}
);
That .findOneAndUpdate() method will return the modified document or otherwise you can just use .update() as a method if you don't need the document returned. The main parts are "matching" the element of the array to update and "identifying" that match with the positional $ as mentioned earlier.
Then of course you are using the $set operator so that only the elements you specify are actually sent "over the wire" to the server. You can take this further with "dot notation" and just specify the elements you actually want to update. As in:
Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{
"$set": {
"permissions.$.role": permission.role
}
},
function(err,doc) {
}
);
So this is the flexibility that MongoDB provides, where you can be very "targeted" in how you actually update a document.
What this does do however is "bypass" any logic you might have built into your "mongoose" schema, such as "validation" or other "pre-save hooks". That is because the "optimal" way is a MongoDB "feature" and how it is designed. Mongoose itself tries to be a "convenience" wrapper over this logic. But if you are prepared to take some control yourself, then the updates can be made in the most optimal way.
So where possible to do so, keep your data "embedded" and don't use referenced models. It allows the atomic update of both "parent" and "child" items in simple updates where you don't need to worry about concurrency. Probably is one of the reasons you should have selected MongoDB in the first place.
In order to validate subdocuments when updating in Mongoose, you have to 'load' it as a Schema object, and then Mongoose will automatically trigger validation and hooks.
const userSchema = new mongoose.Schema({
// ...
addresses: [addressSchema],
});
If you have an array of subdocuments, you can fetch the desired one with the id() method provided by Mongoose. Then you can update its fields individually, or if you want to update multiple fields at once then use the set() method.
User.findById(userId)
.then((user) => {
const address = user.addresses.id(addressId); // returns a matching subdocument
address.set(req.body); // updates the address while keeping its schema
// address.zipCode = req.body.zipCode; // individual fields can be set directly
return user.save(); // saves document with subdocuments and triggers validation
})
.then((user) => {
res.send({ user });
})
.catch(e => res.status(400).send(e));
Note that you don't really need the userId to find the User document, you can get it by searching for the one that has an address subdocument that matches addressId as follows:
User.findOne({
'addresses._id': addressId,
})
// .then() ... the same as the example above
Remember that in MongoDB the subdocument is saved only when the parent document is saved.
Read more on the topic on the official documentation.
If you don't want separate collection, just embed the permissionSchema into the folderSchema.
var folderSchema = new Schema({
name: { type: string },
permissions: [ {
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
} ]
});
If you need separate collections, this is the best approach:
You could have a Permission model:
var mongoose = require('mongoose');
var PermissionSchema = new Schema({
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
});
module.exports = mongoose.model('Permission', PermissionSchema);
And a Folder model with a reference to the permission document.
You can reference another schema like this:
var mongoose = require('mongoose');
var FolderSchema = new Schema({
name: { type: string },
permissions: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Permission' } ]
});
module.exports = mongoose.model('Folder', FolderSchema);
And then call Folder.findOne().populate('permissions') to ask mongoose to populate the field permissions.
Now, the following:
savePermission: function (folderId, permission, callback) {
Folder.findOne({ _id: folderId }).populate('permissions').exec(function (err, data) {
var perm = _.findWhere(data.permissions, { _id: permission._id });
_.extend(perm, permission);
data.markModified("permissions");
data.save(callback);
});
}
The perm field will not be undefined (if the permission._id is actually in the permissions array), since it's been populated by Mongoose.
just try
let doc = await Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{ "permissions.$": permission},
);

Categories

Resources