How can I add to this schema array with mongoose? - javascript

Here's the user schema and the part I want to update is ToDo under User.js (further down). I am attempting to add new data to an array within the db.
data.js
app.post("/data", loggedIn, async (req, res) => {
console.log(req.body.content);
let content = { content: req.body.content };
User.update({ _id: req.user._id }, { $set: req.body }, function (err, user) {
if (err) console.log(err);
if (!content) {
req.flash("error", "One or more fields are empty");
return res.redirect("/");
}
user.ToDo.push(content);
res.redirect("/main");
});
});
User.js
new mongoose.Schema({
email: String,
passwordHash: String,
ToDo: {
type: [],
},
date: {
type: Date,
default: Date.now,
},
})
Originally I was trying the .push() attribute, but I get the error:
user.ToDo.push(content);
^
TypeError: Cannot read property 'push' of undefined

First of all, your problem is the callback is not the user. When you use update the callback is something like this:
{ n: 1, nModified: 1, ok: 1 }
This is why the error is thrown.
Also I recommend specify the array value, something like this:
ToDo: {
type: [String],
}
The second recommendation is to do all you can into mongo query. If you can use a query to push the object, do this instead of store the object into memory, push using JS function and save again the object into DB.
Of course you can do that, but I think is worse.
Now, knowing this, if you only want to add a value into an array, try this query:
var update = await model.updateOne({
"email": "email"
},
{
"$push": {
"ToDo": "new value"
}
})
Check the example here
You are using $set to your object, so you are creating a new object with new values.
Check here how $set works.
If fields no exists, will be added, otherwise are updated. If you only want to add an element into an array from a specified field, you should $push into the field.
Following your code, maybe you wanted to do something similar to this:
model.findOne({ "email": "email" }, async function (err, user) {
//Here, user is the object user
user.ToDo.push("value")
user.save()
})
As I said before, that works, but is better do in a query.

Related

Updating nested MongoDB documents is not working

I'm trying to create update functionality for nested documents in a MongoDB app, but coming from my update function I get "Cannot read properties of undefined (reading 'updateOne')" in the browser. That example is for updateOne, but it happens for every mongoose method I've tried.
The function:
function update(req, res) {
Employee.labs.updateOne({_id: req.params.id}, {
reactPh: req.body.reactPh,
pH: req.body.pH,
temperature: req.body.temperature,
dissOx: req.body.dissOx,
ammonia: req.body.ammonia,
date: req.body.date,
})
Employee.labs.save(function(err) {
res.redirect('/labs')
})
}
"Employee" is the name of the schema/model, and the "labs" schema is embedded into it.
I've been using mongoose methods to create, index, show, delete these same subdocs with no such issues.
Here is the form action with one of the fields:
<form action="/labs/<%= lab._id %>?_method=PUT" method="POST">
<label>Reactive phosphorous:
<input type ="number" step="0.01" name="reactPh" value="<%= lab.reactPh %>">
</label><br>
Any help is much appreciated!
Employee schema (one employee object to many lab objects):
var mongoose = require('mongoose');
var labSchema = new mongoose.Schema({
reactPh: Number,
pH: Number,
temperature: Number,
dissOx: Number,
ammonia: Number,
date: Date,
})
var employeeSchema = new mongoose.Schema({
name: String,
email: String,
labs: [labSchema],
googleId: String
})
module.exports = mongoose.model('Employee', employeeSchema);
UPDATE:
Using Luiz's code and a couple small changes in referencing, it now works. Here it is:
function update (req, res) {
Employee.updateOne({ _id: req.body.employeeId, 'labs._id': req.params.id },
{ $set: {
'labs.$.reactPh': req.body.reactPh,
'labs.$.pH': req.body.pH,
'labs.$.temperature': req.body.temperature,
'labs.$.dissOx': req.body.dissOx,
'labs.$.ammonia': req.body.ammonia,
'labs.$.date': req.body.date,
}}, (err) => {
res.redirect('/labs');
})
}
The mongoose methods, like updateOne, have to be called from the model object itself, the object embedded selection is within the method, like this:
function update(req, res) {
Employee.updateOne({_id: req.params.id},
{ $set: {
'labs.reactPh': req.body.reactPh,
'labs.pH': req.body.pH,
'labs.temperature': req.body.temperature,
'labs.dissOx': req.body.dissOx,
'labs.ammonia': req.body.ammonia,
'labs.date': req.body.date,
}
}, (err) => {
res.redirect('/labs')
})
}
Also, updateOne will do execute the whole updating job, so you don't need to use the save method.
EDIT
Before seeing the schema, I thought that labs was an object, not an array.
To do this update, you'll need to match the object inside the array, I will consider that you have the _id (mongoose generates one automatically) of the labs object that you are trying to update. This is not the only way to match it, you can do it by another of the attributes in the object.
To indicate that you are updating an element of the array, you'll also need to add a .$. in the $set stage.
The answer is:
function update(req, res) {
Employee.updateOne({_id: req.params.id, 'labs._id': req.params.labsId },
{ $set: {
'labs.$.reactPh': req.body.reactPh,
'labs.$.pH': req.body.pH,
'labs.$.temperature': req.body.temperature,
'labs.$.dissOx': req.body.dissOx,
'labs.$.ammonia': req.body.ammonia,
'labs.$.date': req.body.date,
}
}, (err) => {
res.redirect('/labs')
})
}

Mongoose find by parameters including sub-array

I am creating an app where a job is created and that job's id is added to another collection (client) so the job can be referenced from the client itself. I have been able to add the job's id to the client's collection so far, but I am having trouble figuring out how to remove the job's id from the client's collection if the job is deleted. This is because the id is stored as a sub-collection within the client. The code I am trying to get to work is below:
// delete
app.delete("/jobs/:id", function(req, res){
Client.find({jobs._id: req.params.id}, function (err, foundClient){ //This part doesn't work
if (err) {
console.log(err);
} else {
// Add id identifier to Client
foundClient.jobs.pull(req.params.id);
foundClient.save();
}
});
// Delete Job
Job.findByIdAndRemove(req.params.id, function(err, deletedJob){
if (err){
console.log(err)
} else {
// Redirect
res.redirect("/jobs");
}
});
});
I am trying to get the logic of this part to work:
Client.find({jobs._id: req.params.id},
Here is the Client Schema
// =======================Client Schema
var clientSchema = new mongoose.Schema({
organization_name: String,
first_name: String,
middle_name: String,
last_name: String,
email_address: String,
phone_number: String,
street: String,
city: String,
state: String,
zip: String,
description: String,
active: {type: Boolean, deafult: true},
date_added: {type: Date, default: Date.now},
transactions: [{type: mongoose.Schema.Types.ObjectID, ref: "Transaction"}],
jobs: [{type: mongoose.Schema.Types.ObjectID, ref: "Job"}]
});
module.exports = mongoose.model("Client", clientSchema);
Basically, what I am trying to tell it to do is find the Client where the client's jobs array contains an id equal to the id of the job being deleted. Of course, this syntax is incorrect, so it does not work. I have not been able to find documentation that explains how I would be able to do this. Is there a more straightforward way of doing this, the way I am writing it out here? I know that I can query the db this way if the job itself was not an array and only contained one singular variable. Is there a way to do this or do I need to write a completely separate looping function to get this to work? Thank you.
jobs is an array of ids, so to find some documents in Client collection that have req.params.id in the jobs array, the query should be something like this
Client.find({jobs: req.params.id})
this will return an array of documents, each document has an array of jobs Ids
If you are sure that the req.params.id exists only in one document, you can use findOne instead of find, and this will return only one document with an array of jobs Ids
this is regarding the find part
regarding the remove job Id from jobs array, we can use one of the following methods
1- as you suggested, we can find the clients documents that have this job Id first, then remove this id from all the jobs arrays in all matching documents
like this
Client.find({ jobs: req.params.id }, async function (err, foundClients) {
if (err) {
console.log(err);
} else {
// loop over the foundClients array then update the jobs array
for (let i = 0; i < foundClients.length; i++) {
// filter the jobs array in each client
foundClients[i].jobs = foundClients[i].jobs || []; // double check the jobs array
foundClients[i].jobs = foundClients[i].jobs.filter(jobId => jobId.toString() !== req.params.id.toString());
// return all the jobs Ids that not equal to req.params.id
// convert both jobId and req.params.id to string for full matching (type and value)
await foundClients[i].save(); // save the document
}
}
});
2- we can use $pull array update operator to update the jobs array directly
something like this
Client.updateMany(
{ jobs: req.params.id }, // filter part
{
$pull: { jobs: { $in: [req.params.id] } } // update part
},
function (err) {
if (err) {
console.log(err);
} else {
console.log('job id removed from client documents successfully');
}
}
);
hope it helps

return single object based on ObjectId

hi im currently building something for fun that allows users to post anything. and im experincing some problems here is my code.
return details.findOne({'data': {$elemMatch: {'_id':req.params.id}}}).then((a) => {
return res.render('post', { a });
}).catch(err => console.log(err));
i only want this to return one post i thought by using the objectId id would be able todo that but it seems to return everything in the data array anybody have any ideas below is my schema.
i need this to only return the single object whos objId is in the url
var schemaMode = new mongoose.Schema({
email: {type: String, required: true},
password: {type: String, required: true},
username: {type: String,required:true},
data: [{
author: String,
title: String,
comments: [String],
article: String,
}]
});
Details.findById(req.params.id, function(err, foundObject){
//foundObject contains the object with the ._id matching req.params.id
});
if you only want certain fields back, like you want the object to only have its data field for example, then you do:
Details.findById(req.params.id, "data",
function(err, foundObject){
//foundObject contains the object with the ._id matching req.params.id
});
OR
Details.findById(req.params.id).select("data")
.exec(function(err, foundObject){
//foundObject contains the object with the ._id matching req.params.id
});
to be clear, in the code above, Details is the imported (with require) schema (in your case, the one named schemaMode)
Is "data" your "post"? If so, I think you need a projection.
return details.findOne({'data': {$elemMatch: {'_id':req.params.id}}},{'data.$'}).then((a) => {
return res.render('post', { a.data[0] });
}).catch(err => console.log(err));
'data.$' will project you the whole model, filled only with the desired "data"/"post"

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},
);

Mongodb save into nested object?

I can't get my req.body inserted into my mongodb collection.
I have this route that triggers the add method and inside I am trying to figure out a query to save the req.body into a nested collection array
router.post('/api/teams/:tid/players', player.add);
add: function(req, res) {
var newPlayer = new models.Team({ _id: req.params.tid }, req.body);
newPlayer.save(function(err, player) {
if (err) {
res.json({error: 'Error adding player.'});
} else {
console.log(req.body)
res.json(req.body);
}
});
}
Here is an example document
[
{
"team_name":"Bulls",
"_id":"5367bf0135635eb82d4ccf49",
"__v":0,
"players":[
{
"player_name":"Taj Gibson",
"_id":"5367bf0135635eb82d4ccf4b"
},
{
"player_name":"Kirk Hinrich",
"_id":"5367bf0135635eb82d4ccf4a"
}
]
}
]
I can't figure out how to insert/save the POST req.body which is something like
{
"player_name":"Derrick"
}
So that that the new req.body is now added into the players object array.
My question is how do I set the mongodb/mongoose query to handle this?
P.S I am obviously getting the error message because I don't think the query is valid, but it's just kind of an idea what I am trying to do.
Something like this is more suitable, still doesn't work but its a better example I guess
var newPlayer = new models.Team({ _id: req.params.tid }, { players: req.body });
If you created a Team model in Mongoose then you could call the in-built method findOneAndUpdate:
Team.findOneAndUpdate({ _id: req.params.tid },
{ $addToSet: { players: req.body} },
function(err, doc){
console.log(doc);
});
You could do findOne, update, and then save, but the above is more straightforward. $addToSet will only add if the particular update in question doesn't already exist in the array. You can also use $push.
The above does depend to an extent on how you have configured your model and if indeed you are using Mongoose (but obviously you asked how it could be done in Mongoose so I've provided that as a possible solution).
The document for $addToSet is at http://docs.mongodb.org/manual/reference/operator/update/addToSet/ with the relevant operation as follows:
db.collection.update( <query>, { $addToSet: { <field>: <value> } } );

Categories

Resources