Update element in Array of Mongoose schema - node.js

I am trying to update one element of snippets in my mongoose schema.
My Mongoose schema.
const Schema = new mongoose.Schema({
// ...
createdAt: Date,
snippets: {} // here I push ['string..', ['array of strings..']]
})
Here's a view of snippets in Compass.
Problem with the code below is that it completely erases other elements stored, other than that it works. Unable to specify that I want to update snippets[0], not entire thing..?
User.findOneAndUpdate({ username: req.session.user.username },
{ $set: { snippets: [snippet] } }, callback)
Tried using findOne andsave but it wouldn't update the db.
const snippet = [req.body.code, [req.body.tags]]
User.findOne({ username: req.session.user.username }, function (err, fetchedUser) {
if (err) console.log(err)
fetchedUser.snippets[req.params.id] = snippet // should be set to new snippet?
fetchedUser.save(function (err, updatedUser) {
if (err) console.log(err)
console.log('edited')
// ...
})
})
Any suggestions?

I thought I tried this earlier, but apparantly not.
Using fetchedUser.markModified('snippets') solved my issue with findOne/save not actually saving to DB.

Related

Updating a Subdocument array INSIDE another Subdocument array Mongoose

I am at my wits end with something that is seemingly straightforward:
I need to be able to push new gifts into the Events Array under the specific user. Because each event will have numerous gifts added, I want to keep them all under the user, as they are the one creating the event, and the gifts will live inside of their event where they belong.
The PROBLEM is: when I use the mongoose method 'findByIdAndUpdate', I can only find the main user, and from there, push an event to the events array. What I NEED to be able to do: push gifts to a specific event under that user. I am using mongoose Subdocuments. See my schema below and how I have a subdocument schema (EventSchema) inside of the main user schema, and a subdocument (gift) schema inside the event schema.
SCHEMA:
const Schema = mongoose.Schema;
let giftArr = new Schema({
giftname: String,
giftlink: String,
claimed: Boolean,
claimee: String
})
let eventSchema = new Schema({
eventname: String,
eventowner: String,
date: {
type: Date,
default: Date.now
},
attendees: [
{
attendeename: String
}
],
gift: [giftArr]
})
let userSchema = new Schema({
username: String,
email: { type: String, required: false },
events: [eventSchema]
});
Here are my controllers for my POST & GET routes:
export const insertEventsById = ((req, res) => {
const update = { $push: { events: req.body } }
const id = req.params.userID
Gift.findByIdAndUpdate(id, update, (err, data) => {
if (err) {
console.log(err);
} else {
res.json(data)
console.log(data);
}
})
})
export const getUserById = (req, res) => {
Gift.findById(req.params.userID, (err, user) => {
if(err){
res.send(err)
}
res.json(user)
})
}
To further illustrate, here is my postman GET request for a USER. I can push to the 'events' array (red arrow) as my findByIdAndUpdate method shows above, but when I attempt to go one nested level deeper, into the gift array (green arrow), I cannot find any documentation on that.
I been up and down the mongoose subdocuments and queries pages, and I cannot find a method that will pull specifically the '_id' of the particular event I need. I have even tried the methods on the embedded schemas to specifically look for _id's that way.
Can someone point out where I am going wrong here? Thanks in advance...as always fellow Stacks.

Why does Mongodb update work but mongoose doesn't?

When I query monodb shell, I AM able to update the document.
This is the mongodb command I use:
db.users.update({name:"bob"}, {$set: {email:"newEmail#gmail.com} })
But when I try to update it with mongoose, it doesn't work.
What am I missing??
This is the code in mongoose:
// Create the users schema
var userSchema = mongoose.Schema({
name: String,
email: String
}, {collection: "users"});
// Create a model
var userModel = mongoose.model("userModel", userSchema);
// Update a document
userModel.update({name:"bob"}, {$set: {email:"newEmail#gmail.com"}} );
You should wait for the callback to see if the operation was succesful or not
userModel.update({ name: "bob" },
{$set: { email:"newEmail#gmail.com" }},
function (err, user) {
if (err) return handleError(err);
res.send(user);
});
The mongoose is working asynchronously, you should wait for the response in the callback. There is also a synchrone way to do that but With node is not recommended you will block the stack.
You can use this if you don't need the result in callback
userModel.update({name:"bob"}, {$set: {email:"newEmail#gmail.com"}}).exec();

Mongoose won't update new field after updating schema declaration

I just noticed that when i update my schema definition and add a field, for instance "name: String" and then try to use
People.update( { _id: user_id }, { $set: { name: 'something' } } )
mongoose won't update my property.
I keep getting nModified: 0 on the response.
The only way i found to fix it, is to Drop the collection and then the new documents will work perfectly.
Am i missing something? Does mongoose somehow "caches" the schema of a collection on mongodb itself and then needs a "drop" in order to "reload" the properties?
I think findbyidandupdate will do the task for you. Try with this link
Mongoose - findByIdAndUpdate - doesn't work with req.body
Can you please share your People model and also please use callback with update.
see below and works fine..
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var User = mongoose.model('User', { email: String, name: String });
//First added only email and used save to save the document.
//var user = new User({ email: 'john.due#example.com' });
User.update({ _id: '55fbbb268e7307dc0bf9ae92' }, { $set: { name: 'John Due' }}, function(err, result) {
if(err) throw err;
console.log(result)
});
Stop your node app and restart the app. It should work.

TypeError: Object.keys called on non-object when updating document

I'm trying to update an existing document by increment a counter and pushing an object to an array.
Here's the schema for User:
var UserSchema = new Schema({
[...]
posts: {
totalWords: { type: Number, min: 0, default: 0 },
_entries: [
{
words: { type: Number, min: 0 },
body: { type: String },
date: Date
}
]
},
});
And here's the update code:
var newPost = {
words: req.body.words,
body: req.body.entry,
date: new Date()
};
User.findOne(req.user._id, function (err, user) {
var previous = user.posts.totalWords;
user.posts.totalWords = previous + newPost.words;
user.posts._entries.push(newPost);
user.save(function (err) {
if (err) return res.send(400, err);
return res.json(newPost);
});
});
I get the following error:
[TypeError: Object.keys called on non-object]
Any ideas on how to solve this?
Answering my own question
I was able to solve the problem by changing:
User.findOne(req.user._id, function (err, user) { [...] });
Into this:
User.findById(req.user._id, function (err, user) { [...] });
I think, if you would like to use findOne you need follow syntax:
User.findOne({'_id': req.user._id}, {}, function (err, user) {
...
});
Not sure about findById() vs. findOne(), but I've had problems with Mongoose objects returning Object.keys called on non-object while saving or updating with mal-formed data or data corresponding to an older schema. While initializing the document, Mongoose was expecting a subdocument of some kind, but the data in the database didn't match that.
For me it usually happens when I change schemas from a simple object (String, Number, etc.) to a more complex subdocument of some kind. The schema types are mixed up and the object won't load, not even to fix the problem. I had to go into my database using the native driver, search for mal-formed documents using the $type operator, and update them individually.

Automatically remove referencing objects on deletion in MongoDB

Let's suppose I have a schema like this:
var Person = new Schema({
name: String
});
var Assignment = new Schema({
name: String,
person: ObjectID
});
If I delete a person, there can still be orphaned assignments left that reference a person that does not exist, which creates extraneous clutter in the database.
Is there a simple way to ensure that when a person is deleted, all corresponding references to that person will also be deleted?
You can add your own 'remove' Mongoose middleware on the Person schema to remove that person from all other documents that reference it. In your middleware function, this is the Person document that's being removed.
Person.pre('remove', function(next) {
// Remove all the assignment docs that reference the removed person.
this.model('Assignment').remove({ person: this._id }, next);
});
If by "simple" you mean "built-in", then no. MongoDB is not a relational database after all. You need to implement your own cleaning mechanism.
The remove() method is deprecated.
So using 'remove' in your Mongoose middleware is probably not best practice anymore.
Mongoose has created updates to provide hooks for deleteMany() and deleteOne().
You can those instead.
Person.pre('deleteMany', function(next) {
var person = this;
person.model('Assignment').deleteOne({ person: person._id }, next);
});
In case if anyone looking for the pre hook but for deleteOne and deleteMany functions this is a solution that works for me:
const mongoose = require('mongoose');
...
const PersonSchema = new mongoose.Schema({
name: {type: String},
assignments: [{type: mongoose.Schema.Types.ObjectId, ref: 'Assignment'}]
});
mongoose.model('Person', PersonSchema);
....
const AssignmentSchema = new mongoose.Schema({
name: {type: String},
person: {type: mongoose.Schema.Types.ObjectId, ref: 'Person'}
});
mongoose.model('Assignment', AssignmentSchema)
...
PersonSchema.pre('deleteOne', function (next) {
const personId = this.getQuery()["_id"];
mongoose.model("Assignment").deleteMany({'person': personId}, function (err, result) {
if (err) {
console.log(`[error] ${err}`);
next(err);
} else {
console.log('success');
next();
}
});
});
Invoking deleteOne function somewhere in service:
try {
const deleted = await Person.deleteOne({_id: id});
} catch(e) {
console.error(`[error] ${e}`);
throw Error('Error occurred while deleting Person');
}
You can leave the document as is, even when the referenced person document is deleted. Mongodb clears references which point to non-existing documents, this doesn't happen immediately after deleting the referenced document. Instead, when you perform action on the document, e.g., update. Moreover, even if you query the database before the references are cleared, the return is empty, instead of null value.
Second option is to use $unset operator as shown below.
{ $unset: { person: "<person id>"} }
Note the use of person id to represent the value of the reference in the query.
you can use soft delete. Do not delete person from Person Collection instead use isDelete boolean flag to true.
Use $pull. Suppose you have a structure like this.
Stuff Collection:
_id: ObjectId('63dd23c633c17a718c4c5db7')
item: "Item 1"
user: ObjectID('63de669153bc12ecb9081b9e')
User collection:
_id: ObjectId('63de669153bc12ecb9081b9e')
stuff: array[ObjectId('63dd23c633c17a718c4c5db7'), ObjectId('63de3a69715ec134e161b0ea')]
Then after you remove the stuff:
const stuff = Stuff.findById(req.params.id)
const user = User.findById(req.params.id)
await stuff.remove()
// here you can use $pull to update
await user.updateOne({
$pull: {
stuff: stuff.id
}
})
you can simply call the model that needs to be deleted and delete that document like this:
PS: This answer is not specific to the question schema.
const Profiles = require('./profile');
userModal.pre('deleteOne', function (next) {
const userId = this.getQuery()['_id'];
try {
Profiles.deleteOne({ user: userId }, next);
} catch (err) {
next(err);
}
});
// in user delete route
exports.deleteParticularUser = async (req, res, next) => {
try {
await User.deleteOne({
_id: req.params.id,
});
return res.status(200).json('user deleted');
} catch (error) {
console.log(`error`, error);
return next(error);
}
};

Resources