Mongo's updateMany with rename doesn't update for some fields - node.js

Desctiption
MongoDB's updateMany method doesn't update any documents when used with $rename on some fields.
Example
I try to rename the fields blog.blog_ttile and blog.blog_cotnet. I checked for typos, there are none.
Example model:
User: {
name,
email,
blog: {
blog_ttile,
blog_cotnet
}
}
Code:
const mongoose = require('mongoose');
const User = mongoose.model('User');
const nameChanges = {
"blog.blog_ttile": 'blog.title',
'blog.blog_cotnet': 'blog.content',
};
async function performNameChanges() {
try {
const updatedDocuments = await User.updateMany({}, { $rename: nameChanges });
console.log({ updatedDocuments });
} catch(err) {
console.error(err);
}
}
returns:
{ updatedDocuments: { ok: 0, n: 0, nModified: 0 } }
Additional details
Some fields are correctly recognized. Let's say email in the above example. However, when I try to update the updated name, it doesn't work again. Interestingly, it still detects the original name.
Example:
Renaming email to personal_email works. Renaming personal_email to email afterwards doesn't and returns { ok: 0, n: 0, nModified: 0 }. Calling a rename on email a second time returns { n: <total_records>, nModified: 0, ok: 1 } although no documents have email anymore`.
What could be causing this?
Note:
This question applies for MongoDB without Mongoose with db.getCollection("User").updateMany instead of User.updateMany

I tried doing the same in MongoDB. It works as expected. In your case I'm suspecting Mongoose schema to be the cause of this weird behavior. Mongoose Schema has to have the field you are looking to rename. If the field doesn't exist, it returns nModified 0. The schema will need to have both the old and the new names. Old ones to allow the migration, and the new ones for the new logic in the code.
Your return result is:
{ updatedDocuments: { ok: 0, n: 0, nModified: 0 } }
How is this possible? n=0? for query {}. It is only possible when there are no elements in your collection. n means matched count, it must be equal to total number of records in your collection.
Renaming email to personal_email works
Before first update your schema is fine. But after rename (first update), you should update your schema to :
User: {
name,
personal_email,
blog: {
blog_tile,
blog_contnet
}
}
before running the second update (renaming back to email).

As said in the other answer, it's because your mongoose schema didn't contain the field you wanted to rename.
Instead of keeping the old field around while the migration occurs, you can also specify strict: false in the options, and mongoose will not discard unknown paths:
const mongoose = require('mongoose');
const User = mongoose.model('User');
const nameChanges = {
"blog.blog_ttile": 'blog.title',
'blog.blog_cotnet': 'blog.content',
};
async function performNameChanges() {
try {
const updatedDocuments = await User.updateMany(
{},
{ $rename: nameChanges },
{
// Strict allows to update keys that do not exist anymore in the schema
strict: false,
}
).exec();
console.log({ updatedDocuments });
} catch(err) {
console.error(err);
}
}

use like this thanks you
import { connect } from '../database';
export const renameFileds = async () => {
const db = connect();
//let userlist = await db.UserModel.find();
const updatedDocuments = await db.UserModel.updateMany(
{},
{ $rename: { image: 'picture' } },
{
// Strict allows to update keys that do not exist anymore in the schema
strict: false,
}
).exec();
console.log({ updatedDocuments });
};
renameFileds();

Related

Firebase Cloud Function batch write update document overwrote the entire document?

The following Cloud Function has a batch write operation that, in part, updates a single field in a document. This overwrote the entire document and now the document has a single field joinedCount: -1. Is this not the way to update individual fields in documents without overwriting them?
exports.deleteUserTEST = functions.https.onCall(async (data, _context) => {
const uId = data.userId;
const db = admin.firestore();
try {
const batch = db.batch();
const settingsDoc = await db.collection("userSettings").doc(uId).get();
const joinedIds = settingsDoc.get("private.joinedIds");
Object.keys(joinedIds).forEach(function(jId, _index) {
batch.update(
db.collection("profiles").doc(jId),
{
private: {
joinedCount: admin.firestore.FieldValue.increment(-1), // <-- the culprit
},
},
);
});
await batch.commit();
} catch (error) {
throw new functions.https.HttpsError("unknown", "Failed the delete the user's content.", error);
}
return Promise.resolve(uId);
});
Moving the solution found in the comments by #Dharmaraj into a community answer, this problem was caused by the structure of the document.
Since all the data in the document was inside the private map field, passing a new map through the update method would make it appear that the entire document was being overwritten instead of updated.
In this case, you would need to access the fields through dot notation. This allows those inner fields within the map to be updated, without replacing the entire private map:
Object.keys(joinedIds).forEach(function(jId, _index) {
batch.update(db.collection("profiles").doc(jId), {
"private.joinedCount": admin.firestore.FieldValue.increment(-1)
});
});
Another example from the documentation:
import { doc, setDoc, updateDoc } from "firebase/firestore";
// Create an initial document to update.
const frankDocRef = doc(db, "users", "frank");
await setDoc(frankDocRef, {
name: "Frank",
favorites: { food: "Pizza", color: "Blue", subject: "recess" },
age: 12
});
// To update age and favorite color:
await updateDoc(frankDocRef, {
"age": 13,
"favorites.color": "Red"
});

Mongoose - remove _id from lean ( using toJSON() ?)

I'm having a NODE.JS project using mongoose 5.x
My model have toJSON method which removes the _id & __v fields perfectly
mySchema.method("toJSON", function toJSON() {
const {__v, _id, ...object} = this.toObject();
return {
id: _id,
...object
};
});
so when fetching data from the db:
const data = myModel.findOne({_id: id});
I get an object that when serialized to the user:
res.json(data);
It doesn't contain the _id and __v fields as required.
The problem is when I use lean():
const data = myModel.findOne({_id: id}).lean();
the data object contains those fields.
I can remove them manually when using lean
but I would prefer to find a way to sanitize the data object in both cases with the same mechanism.
any suggestions?
Thanks in advance.
Not sure if this is what you want but maybe:
const data = myModel.findOne({_id: id}).lean().then(res => {
delete res._id
return res
})
When JSON.stringify() is called on an object, a check if done if it has a property called toJSON. It's not specific to Mongoose, as it works on plain objects:
const myObj = {
_id: 123,
foo: 'bar',
// `toJSON() {...}` is short for `toJSON: function toJSON() {...}`
toJSON() {
// Because "this" is used, you can't use an arrow function.
delete this._id;
return this;
}
};
console.log(JSON.stringify(myObj));
// {"foo":"bar"}
Mongoose doesn't have an option to automatically inject a toJSON function into objects returned by lean(). But that's something you can add.
First, create a function that:
Takes an object with properties
Listens to when Mongoose runs a find query
Tells Mongoose that after the query, it should change the result
The change: merge the result with the object from step 1.
function mergeWithLeanObjects(props) {
// Return a function that takes your schema.
return function(schema) {
// Before these methods are run on the schema, execute a function.
schema.pre(['find', 'findOne'], function() {
// Check for {lean:true}
if (this._mongooseOptions.lean) {
// Changes the document(s) that will be returned by the query.
this.transform(function(res) {
// [].concat(res) makes sure its an array.
[].concat(res).forEach(obj => Object.assign(obj, props));
return res;
});
}
});
};
}
Now add it to your schema:
mySchema = new mongoose.Schema({
foo: String
});
mySchema.plugin(mergeWithLeanObjects({
toJSON() {
delete this._id;
delete this.__v;
return this;
}
}));
Then test it:
const full = await myModel.findOne();
const lean = await myModel.findOne().lean();
console.log(full);
// Logs a Mongoose document, all properties.
{ _id: new ObjectId("62a8b39466768658e7333154"), foo: 'bar', __v: 1 }
console.log(JSON.stringify(full));
// Logs a JSON string, all properties.
{"_id":"62a8b39466768658e7333154","foo":"bar","__v":1}
console.log(lean);
// Logs an Object, all properties.
{ _id: new ObjectId("62a8b39466768658e7333154"),
foo: 'bar', __v: 1, toJSON: [Function: toJSON] }
console.log(JSON.stringify(lean));
// Logs a JSON string, filtered properties.
{"foo":"bar"}
If you want to re-use the plugin with the same settings on multiple schemas, just save the function that mergeWithLeanObjects returns somewhere.
// file: clean.js
module.exports = mergeWithLeanObjects({
toJSON() {
delete this._id;
delete this.__v;
return this;
}
});
// file: some-schema.js
schema1.plugin(require('./clean.js'));
// file: some-other-schema.js
schema2.plugin(require('./clean.js'));
There's also mongoose.plugin() to add the function to all schemas.
Try this to retrieve the _id from a document
myModel.findOne({_id: id}, function(err, doc) {
if(err)
return 'do something with this err'
console.log(doc._id)
})

MongoDB updateOne returning null for upsertedId

I'm trying to get the upsertedId for the updated doucment, but it's returning null.
Here's the function for updating a doucment and trying to return result.upsertedId:
async function run() {
try {
await client.connect()
const collection = client.db("database").collection("collection")
const filter = { username: `${username}`}
const options = { upsert: true };
const updateDoc = {
$set: { date: new Date(), topartists: data.body.items, toptracks: toptracks }
};
await collection.updateOne(filter, updateDoc, options)
.then(result => {
console.log(`${result.matchedCount} document(s) matched the filter, updated ${result.modifiedCount} document(s)`)
console.log(result.upsertedId)
})
console.log('done1')
} finally {
await client.close();
}
}
and here's the console output of the function:
1 document(s) matched the filter, updated 1 document(s)
null
done1
I'm just starting with MongoDB, and the end goal here is to get
_id. It works there is nothing in my database (when it has to make a new document instead of updating one).
That will not be the case.
Your log clearly says - your query matched 1 document and it updated one document.
If your update results in insert, then response of your update operation will have upsert ids. - Means, matched count is 0.

NodeJS Mongoose updateOne giving no match every time

I am trying to update a document in mongo with mongoose using updateOne method:
const updateResult = await UserModel.updateOne({
_id: mongoose.Types.ObjectId(userId)
}, {
$set: {
a: 'B'
}
})
userId contains a string of the ID of the user.
I have tried using the following
1. { _id: userId }
2. { email: theEmailOfTheUser }
But still, the updateResult is
n:0, nModified:0, ok:0
So I think it's must be something with the method itself and not in my query.
Also, when I'm trying to find the user using the query below, it can find it:
const user = await UserModel.find({
_id: userId
});
//user is found
Actually mongoose takes care of the $set and you do not have to add it. just:
const updateResult = await UserModel.updateOne({
_id: userId
}, {
a: 'B'
})
but the better solution would to just use findByIdAndUpdate():
const updateResult = await UserModel.findByIdAndUpdate(userId, {
a: 'B'
})

mongoose find not fetching any result

I've simple collection in mongo and a corresponding mongoose model. This collection will only contain one document always. When I run a query in mongoshell it is giving me the result, but when I try to do findOne using mongoose it is not returning any result at all. Can someone help me figure out what is wrong. Below is my code.
Model:
const mongoose = require('mongoose');
const schema = new mongoose.Schema({
lastResetMonth: {
type: Number
},
lastResetWeek: {
type: Number
},
currentFisYear:{
type: Number
}
});
module.exports = mongoose.model('ReserveResetTrack', schema, 'reserveResetTrack');
const ReserveResetTrack = require('../models/ReserveResetTrack');
ReserveResetTrack.findOne({})
.then(trackData => {
return {
lastFisMonth: trackData.lastMonth,
lastFisWeek: trackData.lastWeek
}
});
The above code is always returning nothing but a promise.
This is the only document i've in my collection and this will be the only one for ever
{
"_id" : ObjectId("589271a36bfa2da821b13ce8"),
"lastMonth" : 0,
"lastWeek" : 0,
"currentYear" : 0
}
Use exec() like this:
ReserveResetTrack.findOne({})
.exec() // <--- use exec() here
.then(trackData => {
return {
lastFisMonth: trackData.lastMonth,
lastFisWeek: trackData.lastWeek
}
});

Resources