Save object to array in another model - javascript

So I got two mongoose-models:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var eventSchema = new mongoose.Schema({
name: String,
date: String,
dogs: [{ type: Schema.Types.ObjectId, ref: 'Dog' }]
});
module.exports = mongoose.model('Event', eventSchema);
and
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var dogSchema = new mongoose.Schema({
name: String,
age: String,
gender: String,
});
module.exports = mongoose.model('Dog', dogSchema);
Event contains an array of dogs and im trying to figure out how to add/delete dogs to this array.
On the client I got this method:
$.ajax({
url: "http://localhost:3000/api/events/",
dataType: 'json',
type: 'POST', // Not sure if I should Post or Put...
data: {event_Id : this.props.choosenEvent._id, //Here I got the Id of the Event that i want to update by
dog_Id : this.props.events[dog]._id }, //adding this dog, which Id is here
success: function(data) {
}.bind(this),
});
},
On the server, NodeJs, I got my routes to the API. To me, it makes sense to use an PUT-method and start by getting the right Event with the event_Id passed as a param. Something like:
router.route('/events/:event_id')
.put(function(req, res) {
Event
.findById({ _id: req.param.event_id })
.populate('dogs')
});
But Im stuck at this point. Any help appreciated. Thanks!
Update!
Thank you! Your code helped a lot, you used lodash .remove to delete a dog from the array, is there a similar way to add an item with lodash?
I gave the add method a go like this:
router.route('/events')
.post(function(req, res) {
// Your data is inside req.body
Event
.findById({ _id: req.body.event_Id })
// execute the query
.exec(function(err, eventData) {
// Do some error handing
// Your dogs are inside eventData.dogs
eventData.dogs.push(req.body.dog_Id);
console.log(eventData)
});
// Update your eventDate here
Event.update({_id: req.body.event_id}, eventData)
.exec(function(err, update) {
// Do some error handing
// And send your response
});
});
When I hit the console.log(eventData) I can see that dog_id gets added to the array as it should. However it does not get saved to the db and the error says that eventData is not defined in Event.Update. I suspect this is a Js-scope-issue.
Onte thing that boggles me is this:
Obviously I would like to be able to add and remove dogs from the array and the
route is this: router.route('/events') .
But if both the add-method and the remove-method is on the same route, how can the code know which one I am going for?

There are a few mistakes you are making. First of all, you are making a POST request but your route accepts a PUT request. I have updated your code so it accepts a POST.
When posting objects, your data is inside req.body. req.params is used for url parameters. This is also the case when using a PUT request.
Populating dogs is not really necessary. You are sending your dog_id to your function so you can delete your item from your array which removes your dog from your event. This should do the trick. Please note that this does not remove your dog from your DB but only from your event.
Last but not least. I am using lodash. _.remove is a lodash function. You should definitely check it out, it will help you a lot.
Take a look at my code. It should get you going:
router.route('/events/:event_id')
// Since you are posting, you should use POST from JavaScript instead of PUT
.post(function(req, res) {
// Your data is inside req.body
Event
.findById({ _id: req.body.event_id })
// execute the query
.exec(function(err, eventData) {
// Do some error handing
// Your dogs are inside eventData.dogs
_.remove(eventData.dogs, function(d) {
return d._id === req.body.dog_Id;
});
// Update your eventDate here
Event.update({_id: req.body.event_id}, eventData)
.exec(function(err, update) {
// Do some error handing
// And send your response
});
});
});
UPDATE:
I do not think there is a way to add items to an array with lodash but you can simply use push like you did in your code example. That works just fine.
Your update is not working because your are executing the findById and update at the same time. You will have to find the item first, add the id and THEN update the item :) Move your update function inside the callback of your findById function and that should be fixed. So it looks like this:
router.route('/events')
.post(function(req, res) {
// Your data is inside req.body
Event
.findById({ _id: req.body.event_Id })
// execute the query
.exec(function(err, eventData) {
// Do some error handing
// Your dogs are inside eventData.dogs
eventData.dogs.push(req.body.dog_Id);
console.log(eventData)
// Update your eventDate here
Event.update({_id: req.body.event_id}, eventData)
.exec(function(err, update) {
// Do some error handing
// And send your response
});
});
});
You can add different functions on the same route as long as the method is different from the others. Take a look at REST at this answer. You can have a GET, POST, PUT & DELETE on /events. This is defined by this rule:
router.route('/events').post();

Related

Mongoose search by array entries ($in) behavior not aligned with MongoDB Atlas

I'm running a Node.js server, connecting to a MongoDB database with mongoose.
Inside my controller, I have several methods that make operations to the database. One of them is this one:
async findMultiple(req, res) {
const [baseSkillsArray] = Array(req.body);
try {
// if there is not baseSkillsArray, skip
if (!baseSkillsArray) {
return res.status(200).send([]);
}
// find all baseSkills using the ids in the baseSkillsArray
const allBaseSkills = await BaseSkill.find({
_id: { $in: [baseSkillsArray.baseSkillArray] } //
});
console.log('test ' + allBaseSkills);
res.status(200).send(allBaseSkills);
} catch (error) {
console.error(error.message);
res.status(500).send('Server error find BaseSkills');
}
}
However, this returns me nothing. I did some debugging and I found the reason is the find id $in the array. So I tried hard coding a value, like '2', for instance.
// find all baseSkills using the ids in the baseSkillsArray
const allBaseSkills = await BaseSkill.find({ _id: { $in: ['2'] } });
No success. So I went to MongoDB Atlas, where my DB is stored. I tried filtering using the same line of code in my collections.
{ _id: { $in: ['2'] } }
Surprisingly, it returns my document as I wanted!
The issue is that I need to make it work with mongoose. Any ideas? Is this a known bug?
There is nothing wrong with the query, nor a bug regarding $in.
In fact, what's wrong is the actual collection name. I manually created a collection in MongoDB Atlas, called "baseSkills". However, mongoose by default transforms your collection name into lowercase and adds an "s" if your collection's name is not in the plural.
So every time I started my server, I noticed that there was a new collection called "baseskills". I assumed it was a bug and deleted it. Only after making this post that I realized the collection was there again.
So I exported the documents to this collection and my query was working fine.
FYI, there is a way to enforce the collection's name in mongoose. When you declare you model, add a second parameter to the Schema function called "collection". Here is an example:
const BaseSkillSchema = new mongoose.Schema({
_id: {
type: String,
required: true
}, ...
}, { collection: 'baseSkills' })
That's it! Sorry for the mess and thank you for your help!
you want to query over mongo db object ids. So you should create a new ObjectId to do that.
import {Types} from 'mongoose';
{ _id: { $in: [new Types.Object("2")] } }
Or if you have 2 ids one generated and one custom created as id then you can query without creating a new object.
{ id: { $in: ['2'] } }

Why am I not being able to write into objects I fetched from database?

What I am trying to do: I am fetching data from MongoDB(doc). I has property files which is an array of objects. Schema of the mentioned object:
{
fileName : {type : String, required : true},
fileSize : {type : Number, required : true},
fileOriginalName : String,
sentEarlier : Boolean,
}
and before sending this array(in doc.files) to my frontend, I want to append a property(downloadLink), to each object. The whole code where I encountered the problem:
Router.post('/fetch-more-files', (req, res) => {
let {uuid} = req.body;
File.findOne({uuid}, (err, doc) => {
if(err) {
return res.send({'error' : 'Something went wrong. Refresh the page.'});
}
if(!doc) {
return res.send({'error' : 'Invalid link or link expired'});
}
let newFiles = [];
doc.files.forEach(file => {
file.downloadLink = `${process.env.APP_BASE_URL}/files/download/${uuid}/${file.fileName}`;
});
doc.files.forEach(file => {
if(!file.sentEarlier) newFiles.push(file);
});
doc.save((err, savedDoc) => {
res.json({newFiles});
});
});
});
PROBLEM: The newFiles array which I am populating with all the files(objects inside doc.files array), those that do not have a 'sentEarlier' key true to them, when sent as response, not a single object that array has the 'downloadLink' property to them. I tried so hard and for so long to debug, but failed. Hope to find some insights as to what I could be doing wrong here. Thanks in advance!!!
SOLUTION: After hours of lingering and loitering over this problem, I found a solution. If you want the technical answer to why it didn't work, this is not the place for you. But if you just want your problem solved, so you can finally go pee, read on.
The answer is to use toObject() method which apparently converts the mongoose document to a plain JS object, which is now at your disposal.
toObject() in mongooseJS documentation:
Mongoose v5.11.9:API docs | document.prototype.toObject()
Other answers are most welcome.
Mongoose queries return an instance of the Mongoose Document class. if you want to modify your query response just try to call your mongoose query in a different way.
File.findOne({uuid}, {new: true}).lean().exec((err, doc) => {
if(err) {
return res.send({'error' : 'Something went wrong. Refresh the page.'});
}
// your logic
});
here you get more idea about lean()

document wont save using save() method with mongoose

I have this schema:
var UserSchema = mongoose.Schema({
analytic: {
type: Object,
default: {
today:[],
weekly:[],
monthly:[],
yearly:[],
allTime:[]
}
}
});
let User = mongoose.model("bloger", UserSchema);
module.exports = {User};
and I am trying to save some data into one of the arrays like so:
User.findOne({username:username}, (e, user) => {
if (e) {
res.send('error fetching post')
}
else if (!user) {
res.send('no user found')
}
else if (user) {
user.analytic.today.push(req.body.visitor) // push the data object to the array
user.save((e, doc) => {
if (e) {
res.send(e)
}
if (doc) {
console.log('user saved')
res.send(doc)
}
})
}
})
})
I am getting the doc object on save() and not the e so I though it should have save it but it wasn't.
I have had a similar issue before this is because I am not defining a new Model I am just passing a JSON object.
Instead of saving the object you need to create a new model and save that.
Try creating a new model passing the save into it like below;
var newUser = new User(user);
newUser.save((e, doc) {
if (e) {
res.send(e)
}
if (doc) {
console.log('user saved')
res.send(doc)
}
});
Making sure you require the User Model inside the script.
Performing deep modifications in objects not in your schema makes Mongoose oblivious to those changes, preventing it from knowing what to save (and from making efficient atomic updates). The end result is that, when calling .save, Mongoose thinks there's nothing modified and therefore does nothing.
In your particular scenario, you have two options:
1. Add your analytics sub-arrays to your schema
This is the best option and allows for finer control of everything:
const UserSchema mongoose.Schema({
analytic: {
today: [{}],
weekly: [{}],
monthly: [{}],
yearly: [{}],
allTime: [{}],
}
});
With this, those arrays are now known to Mongoose and everything should work correctly.
Note that you don't need defaults in this case, as Mongoose is smart enough to create the arrays as needed.
2. Manually mark modified object as modified
If for any reason you don't want or can't modify the schema, you can manually mark the analytic object as modifies so Mongoose knows of it:
user.analytic.today.push(req.body.visitor) // push the data object to the array
user.markModified('analytic'); // mark field as modified
user.save(...);
This signals Mongoose that analytic or any of its children have changed and triggers an update when calling .save. Note however that Mongoose views this as a full change in the object, while with option 1 it can use $push instead.

Update Issues with Put Request, JQuery, MongoDB and Mongoose

I'm trying building an application using MongoDB, Mongoose, JQuery, and Node.js. Whenever I try to add to an array within my Schema sometimes it will add that item several times.
Schema
This is my schema, I'm trying to add the my upvote and downvote arrays that keep track of users that voted by their id.
var SuggestionSchema = new mongoose.Schema({
title: String,
content: String,
link: String,
upvote: [{
user_id: {
type: Schema.ObjectId,
ref: 'User'
}
}],
downvote: [{
user_id: {
type: Schema.ObjectId,
ref: 'User'
}
}],
user_id: {
type: Schema.ObjectId,
ref: 'User'
},
category_id: {
type: Schema.ObjectId,
ref: 'Category'
},
});
Route and Query
Here is my put route and query
router.put('/:suggestion_id/downvote', function(req, res, next) {
Suggestion.findByIdAndUpdate(
req.params.suggestion_id,
{$push: {"downvote": req.body}},
function(err, suggestion) {
res.json(suggestion);
})
});
Ajax Call with Jquery
This is my PUT request that is triggered on a click.
$('#downvote').click(function(){
var user = {
user_id: current_user._id
}
$.ajax({
url: current_url + "suggestions/" + current_suggestion + '/downvote',
type: 'PUT',
data: user,
success: function(data){
//callback
}
});
}
Results
Two other things to note:
The console will return:
Failed to load resource: net::ERR_EMPTY_RESPONSE
AND
Ultimately just clicking once sometimes results in 2 or 3 of the same user being pushed into the downvote array in the Schema. I put some safeguards on the front end that checks if the user has already voted, but it still seems to put through multiple times so I think it's a different issue. I believe I'm just approaching it wrong or missing something fundamental. I've only been coding for 6 months so I'm still learning. Thanks!
Instead of using the $push operator to push the user id object to the downvote array, use the $addToSet operator. This adds a value to an array unless the value is already present, in which case $addToSet does nothing to that array. It only ensures that there are no duplicate items added to the set and does not affect existing duplicate elements. If the field is absent in the document to update, $addToSet creates the array field with the specified value as its element. So you final route will look like:
router.put('/:suggestion_id/downvote', function(req, res, next) {
Suggestion.findByIdAndUpdate(
req.params.suggestion_id,
{$addToSet: {"downvote": req.body}},
function(err, suggestion) {
res.json(suggestion);
}
);
});

mongoose update array ObjectId from an array from angular

I'm adding ObjectId to an array from another array that I receive as the body.
exports.updateBasket = function (req, res) {
Basket.findOne({ _id: req.params.id }, function (err, basket) {
for(var i=0, len=req.body.length; i < len; i++) {
basket.update({$addToSet: { "items": req.body[i] } }, { upsert: true, safe: true });
}
if (err) {
res.send(err);
}
else {
res.json({ message: 'Successfully added' });
}
});
};
I have 2 questions concerning this :
Is there any upside to do the loop in angular and have multiple PUT?
What is the way to update this same array but when removing ObjectId?
One way that I thought of was to loop ObjectId that have to be removed and look if they are in the array of the object, if yes, delete them.
Another way would be to clear the array when PUT is called and update with the new ObjectId list (which would be the ones that were there minus the one user removed).
Both doesn't feel right ...
thanks
You code looks a bit odd. You are fetching asynchronously on the req.params._id but you are queuing up req.body.length potential worth of updates, but you send 'success' before you even get a response back from the updated results.
If you wanted to filter on arrays, look at lodash, if you want to process multiple updates asynchronously and get those response use async modules.

Categories