remove an field from mongodb using mongoose - database

this is my model function,
Project.collection.findOneAndUpdate(query, updatedProject)
.then((res) => {
if (res) {
return resolve(res);
} else reject();
})
.catch((err) => {
return reject(err);
});
updatedProject holds json data like,
{
"data":"has data",
"fields":"some data"
}
and query holds,
let query = { _id: mongoose.Types.ObjectId(projectId)}
now my database document project collection data is,
{
"_id":ObjectId("5b446a58ab89ec3cc34c2bec"),
"name":"my name",
"data":"has data123",
"fields":"some data123",
"record":{
"data1":"some records",
"data2":"some records",
"data3":"some records"
}
}
Now, my updatedProject holds only data,fields, so im expecting other fields name, record to be removed, but it responds back with all the fields with updated value,
but if i pass updateProject like this,
"record":{
"data1":"some new records"
}
it just works as expected (removes data2 and data3),
please help me to solve this issue.

Related

How to get all subcollection documents with subcollection name as a date? [duplicate]

Say I have this minimal database stored in Cloud Firestore. How could I retrieve the names of subCollection1 and subCollection2?
rootCollection {
aDocument: {
someField: { value: 1 },
anotherField: { value: 2 }
subCollection1: ...,
subCollection2: ...,
}
}
I would expect to be able to just read the ids off of aDocument, but only the fields show up when I get() the document.
rootRef.doc('aDocument').get()
.then(doc =>
// only logs [ "someField", "anotherField" ], no collections
console.log( Object.keys(doc.data()) )
)
It is not currently supported to get a list of (sub)collections from Firestore in the client SDKs (Web, iOS, Android).
In server-side SDKs this functionality does exist. For example, in Node.js you'll be after the ListCollectionIds method:
var firestore = require('firestore.v1beta1');
var client = firestore.v1beta1({
// optional auth parameters.
});
// Iterate over all elements.
var formattedParent = client.anyPathPath("[PROJECT]", "[DATABASE]", "[DOCUMENT]", "[ANY_PATH]");
client.listCollectionIds({parent: formattedParent}).then(function(responses) {
var resources = responses[0];
for (var i = 0; i < resources.length; ++i) {
// doThingsWith(resources[i])
}
})
.catch(function(err) {
console.error(err);
});
It seems like they have added a method called getCollections() to Node.js:
firestore.doc(`/myCollection/myDocument`).getCollections().then(collections => {
for (let collection of collections) {
console.log(`Found collection with id: ${collection.id}`);
}
});
This example prints out all subcollections of the document at /myCollection/myDocument
Isn't this detailed in the documentation?
/**
* Delete a collection, in batches of batchSize. Note that this does
* not recursively delete subcollections of documents in the collection
*/
function deleteCollection(db, collectionRef, batchSize) {
var query = collectionRef.orderBy('__name__').limit(batchSize);
return new Promise(function(resolve, reject) {
deleteQueryBatch(db, query, batchSize, resolve, reject);
});
}
function deleteQueryBatch(db, query, batchSize, resolve, reject) {
query.get()
.then((snapshot) => {
// When there are no documents left, we are done
if (snapshot.size == 0) {
return 0;
}
// Delete documents in a batch
var batch = db.batch();
snapshot.docs.forEach(function(doc) {
batch.delete(doc.ref);
});
return batch.commit().then(function() {
return snapshot.size;
});
}).then(function(numDeleted) {
if (numDeleted <= batchSize) {
resolve();
return;
}
// Recurse on the next process tick, to avoid
// exploding the stack.
process.nextTick(function() {
deleteQueryBatch(db, query, batchSize, resolve, reject);
});
})
.catch(reject);
}
This answer is in the docs
Sadly the docs aren't clear what you import.
Based on the docs, my code ended up looking like this:
import admin, { firestore } from 'firebase-admin'
let collections: string[] = null
const adminRef: firestore.DocumentReference<any> = admin.firestore().doc(path)
const collectionRefs: firestore.CollectionReference[] = await adminRef.listCollections()
collections = collectionRefs.map((collectionRef: firestore.CollectionReference) => collectionRef.id)
This is of course Node.js server side code. As per the docs, this cannot be done on the client.

Return specific array from object collection

So I get some data into my socket
The code in Client is :
useEffect(() => {
const socket = io("http://localhost:5000/api/socket");
socket.on("newThought", (thought) => {
console.log(thought);
});
}, []);
And then the code in my server is
connection.once("open", () => {
console.log("MongoDB database connected");
console.log("Setting change streams");
const thoughtChangeStream = connection.collection("phonenumbers").watch();
thoughtChangeStream.on("change", (change) => {
io.of("/api/socket").emit("newThought", change);
});
});
When something in my "phonenumbers" collection gets changed I get in return the whole collection . How would I be able to only get the array that got changed from the object in collection?
So for example if in the collection the only service that changed is the one with id "607deefd13c4ebcbcfa0900a" that should be the only one returned and not the whole collection object.
The fullDocument parameter to the options (second) argument to the watch method can be used to get a delta describing the changes to the document for update operations:
const thoughtChangeStream = connection.collection("phonenumbers").watch([], {
fullDocument: 'updateLookup'
});
thoughtChangeStream.on("change", (change) => {
io.of("/api/socket").emit("newThought", change);
});
This will then return a response document like this where updateDescription contains the fields that were modified by the update:
{
_id: {
_data: '8260931772000000012B022C0100296E5A1004ABFC09CB5798444C8126B1DBABB9859946645F696400646082EA7F05B619F0D586DA440004'
},
operationType: 'update',
clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 1, high_: 1620252530 },
ns: { db: 'yourDatabase', coll: 'yourCollection' },
documentKey: { _id: 6082ea7f05b619f0d586da44 },
updateDescription: {
updatedFields: { updatedField: 'newValue' },
removedFields: []
}
}
Note: This will only work for update operations and will not work for replace, delete, insert, etc.
See also:
http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html.
https://docs.mongodb.com/manual/reference/change-events/

findOneAndUpdate causing duplication problem

I am having a problem in findOneAndUpdate in mongoose.
The case is that i am updating a document by finding it.
The query is as follows:
UserModel.findOneAndUpdate({
individualId: 'some id'
}, {
$push: {
supporterOf: 'some string'
}
})
The 'supporterOf' is the ref of UserModel and its type is 'ObjectId'.
The issue i am facing here is that, 'some string' is being pushed twice under 'supporterOf' in the document.
Can anyone tell me that how to push an array element inside the document?
I was having same problem, solution is.
I was keeping await like below.
**await** schema.findOneAndUpdate(queryParms, {
"$push": {
"array1": arrayDetails,
"array2": array2Details
}
}, {
"upsert": true,
"new": true
},
function (error, updateResponse) {
if (error) {
throw new Error (error);
} else {
// do something with updateResponse;
}
});
simply removing await helped me resolving this problem.
Need to find the root cause.
any pointer for references are welcome.
I have recently encountered the same problem. However, I managed to overcome this issue by some other logics (details given below) but couldn't understand the reason behind that why findOneAndUpdate inserting duplicate entries in mongodb.
You can overcome this problem by following logic.
Use findOne or findById instead of findOneAndUpdate to search the document in your collection and then manually update your document and run save().
You can have better idea with this code snippet
return new Promise(function (resolve, reject) {
Model.findOne({
someCondition...
}, function (err, item) {
if (err) {
reject(err);
} else {
item.someArray.push({
someKeyValue...
});
item.save().then((result) => {
resolve(result)
}).catch((err) => {
reject(err)
});
}
}).catch((err) => {
reject(err)
});
});
This will not insert duplicate item. However, if you come to know the reasoning behind duplication, must do update this thread.
The issue seems to stem from combining an await and a callback. I had the same issue until I realised I was using an (err, resp) callback and a .catch(...).
models[auxType].findOneAndUpdate(
filter,
updateObject,
options,
(err, resp)=>{
if (err) {
console.log("Update failed:",err)
res.json(err)
} else if (resp) {
console.log("Update succeeded:",resp)
res.json(resp)
} else {
console.log("No error or response returned by server")
}
})
.catch((e)=>{console.log("Error saving Aux Edit:",e)}); // << THE PROBLEM WAS HERE!!
The problem resolved as soon as I removed the .catch(...) line.
From the mongoose documentation:
"Mongoose queries are not promises. They have a .then() function for co and async/await as a convenience. However, unlike promises, calling a query's .then() can execute the query multiple times."
(https://mongoosejs.com/docs/queries.html#queries-are-not-promises)
Use $addToSet instead of $push, it should solve the problem. I believe there is an issue with the data structure used in the creation of a mongoose 'Model'. As we know push is an array (which allows duplication) operation while addToSet may be a Set operation (Sets do not allow duplication).
The problem with the accepted answer is that it only solves the problem by wrapping it in an unnecessary additional promise, when the findOneAndUpdate() method already returns a promise. Additionally, it uses both promises AND callbacks, which is something you should almost never do.
Instead, I would take the following approach:
I generally like to keep my update query logic separate from other concerns for both readability and re-usability. so I would make a wrapper function kind of like:
const update = (id, updateObj) => {
const options = {
new: true,
upsert: true
}
return model.findOneAndUpdate({_id: id}, {...updateObj}, options).exec()
}
This function could then be reused throughout my application, saving me from having to rewrite repetitive options setup or exec calls.
Then I would have some other function that is responsible for calling my query, passing values to it, and handling what comes back from it.
Something kind of like:
const makePush = async () => {
try {
const result = await update('someObjectId', {$push: {someField: value}});
// do whatever you want to do with the updated document
catch (e) {
handleError(e)
}
}
No need to create unnecessary promises, no callback hell, no duplicate requests, and stronger adherence to single responsibility principles.
I was having the same problem. My code was:
const doc = await model.findOneAndUpdate(
{filter}, {update},
{new: true}, (err, item) => if(err) console.log(err) }
)
res.locals.doc = doc
next();
The thing is, for some reason this callback after the "new" option was creating a double entry. I removed the callback and it worked.
I had the same problem.
I found a solution for me:
I used a callback and a promise (so using keyword "await") simultaneously.
Using a callback and a promise simultaneously will result in the query being executed twice. You should be using one or the other, but not both.
options = {
upsert: true // creates the object if it doesn't exist. defaults to false.
};
await Company.findByIdAndUpdate(company._id,
{ $push: { employees: savedEmployees } },
options,
(err) => {
if (err) {
debug(err);
}
}
).exec();
to
options = {
upsert: true // creates the object if it doesn't exist. defaults to false.
};
await Company.findByIdAndUpdate(company._id,
{ $push: { employees: savedEmployees } },
options
).exec();
UserModel.findOneAndUpdate(
{ _id: id },
{ object }
)
Even if you use _id as a parameter don't forget to make the filter explicit by id
In my case, changing the async callback solved the problem.
changing this:
await schema.findOneAndUpdate(
{ queryData },
{ updateData },
{ upsert: true },
(err) => {
if (err) console.log(err);
else await asyncFunction();
}
);
To this:
await schema.findOneAndUpdate(
{ queryData },
{ updateData },
{ upsert: true },
(err) => {
if (err) console.log(err);
}
);
if (success) await asyncFunction();
The $addToSet instead of $push allowed me to prevent duplicate entry in my mongoDb array field of User document like this.
const blockUserServiceFunc = async(req, res) => {
let filter = {
_id : req.body.userId
}
let update = { $addToSet: { blockedUserIds: req.body.blockUserId } };
await User.findOneAndUpdate(filter, update, (err, user) => {
if (err) {
res.json({
status: 501,
success: false,
message: messages.FAILURE.SWW
});
} else {
res.json({
status: 200,
success: true,
message: messages.SUCCESS.USER.BLOCKED,
data: {
'id': user._id,
'firstName': user.firstName,
'lastName': user.lastName,
'email': user.email,
'isActive': user.isActive,
'isDeleted': user.isDeleted,
'deletedAt': user.deletedAt,
'mobileNo': user.mobileNo,
'userName': user.userName,
'dob': user.dob,
'role': user.role,
'reasonForDeleting': user.reasonForDeleting,
'blockedUserIds': user.blockedUserIds,
'accountType': user.accountType
}
});
}
}
).catch(err => {
res.json({
status: 500,
success: false,
message: err
});
});
}

Output all documents in mongoose

I am using mongoose ODM and have a schema which looks like this:
var banSchema = new Schema({
userid: { type: String, required: true, unique: true },
name: String,
groupid: String,
reason: String,
timestamp: Date
});
I want to output every single user id from all documents in the collection. I am using this query to obtain the userid objects. However I cannot seem to get the full list automatically. I have to manually enter the object number as seeen below:
bot.onText(/\/sync/i, function (msg) {
var fromId = msg.from.id;
var chatId = msg.chat.id;
if (fromId == config.sudo) {
console.log('Sudo Confirmed And Authorized!');
Ban.find({}, function (err, obj) {
console.log(obj[0].userid); // Returns A Single ID
console.log(obj[1].toObject().userid); // Returns a different ID
bot.sendMessage(chatId, obj[1].toObject().useridid);
});
} else {
console.log('Someone Is Trying To Act Like Sudo! *sigh*');
bot.sendMessage(chatId, 'You Are Not A Mod!');
}
});
This however does not return a full list of id's as I want. How could I solve this issue?
The code above is for a telegram bot which on a /sync command it should return a message with all ids from the collection.
Telegram bot API Limits
Due to the API limits, the entire output should be in a single message.
var query = Ban.find({}).select({
"userid": 1,
//Add more column fields here
"_id": 0 //Ensures _id is not displayed
});
var arr = [];
query.exec(function (err, results) {
if (err) throw err;
results.forEach(function (result) {
arr.push(result.userid);
// Add more column fields here;
});
var fixedJoin =arr.join("\n");
console.log(fixed);
bot.sendMessage(chatId, 'List\n\n' + fixedJoin);
});
The easiest way to get all values of a particular field across all docs in the collection is to use distinct:
Ban.distinct('userid', function (err, userids) {
// userids is an array containing all userid values in the collection.
// string.join into a single string for the message.
bot.sendMessage(chatId, 'USER IDs\n\n' + userids.join('\n'));
});
Use this syntax
Ban.find({}).
select('userid').
exec(function(err, result) {
//result is array of userid of all document
});
You can use this syntax:
Ban.find({}, 'userid', function(err, users) {
users.forEach(function(user) {
console.log(user);
bot.sendMessage(chatId, 'users \n' + user);
});
})

MongoDB: how can I find and merge array

I'm trying to find a specific ID in my document and then merge an array to the existing one, for example if I have this array stored in db.friends:
["12","13","14"]
and I send this array: ["12","16","18"], db.friends should contain: ["12","13","14","16","18"]
I'm using underscore library, but I'm not sure I have to (maybe "aggregate" in mongoose?)
Here is what I did, can you tell me where am I wrong?
function saveFollowers(req, res) {
var friends = req.body.friends; // the new array to merge ["54aafe9df4ee360300fc94c7"];
User.findOne({_id: req.user._id}).exec(function (err, user) {
if (err) {
res.jsonp({error: "Error fetching user info"})
} else {
friends = _.extend(friends, user.friends); //user.friends=existing friends we have in db
user.save(function (err) {
if (err) { res.jsonp({error: "Cant save"}); }
console.log("Friends NOW:"+JSON.stringify(friends)); //Here I don't see the merge, also, I can't see it in mongo db.
res.jsonp("success");
});
}
});
Thank you!
With your current implementation, you haven't actually modified the friends key in the returned user object. So rather you can use the union method as
user.friends = _.union(friends, user.friends); //user.friends=existing friends
user.save(function (err) { .. }
Or with ES6 using the spread operator for concatenating the array and Set for creating a distinct set of elements:
user.friends = [...new Set([...friends ,...user.friends])];
user.save(function (err) { .. }
Another alternative is using the aggregation framework, you could utilize the $setUnion operator:
function saveFollowers(req, res) {
var friends = req.body.friends; // the new array to merge ["54aafe9df4ee360300fc94c7"];
User.aggregate([
{ "$match": { _id: req.user._id } },
{
"$project": {
"friends": { "$setUnion": [ "$friends", friends ] }
}
}
]).exec(function (err, results){
if (err) {
res.jsonp({error: "Error fetching user info"})
} else {
User.findByIdAndUpdate(req.user._id,
{ "$set": { "friends": results[0].friends } },
{ "new": true },
function (err, user) {
if (err) { res.jsonp({error: "Cant save"}); }
console.log("Friends NOW: "+ JSON.stringify(user.friends));
res.jsonp("success");
}
);
}
});
}

Resources