Mongoose FInd and remove object from array of a document - javascript

I have a document 'Collection':
{
name: {type: String, required: true, default: "Untitled Collection"},
movies: [
{ the_id: {type: String},
movie_id: {type: String},
tmdb_id: {type: String},
original_title: {type: String},
release_date: {type:Date}
}],
author: {type: String},}
I need to find and remove a specific item from movies []. This is what I have currently, and it does not work.
req.body is an object passed through the data of a POST request and has all the information necessary to be able to match one in the movies[] array
Collection.findOne({_id : req.params.id}, (err, collection) =>{
if(err){res.status(400).json(err);}
if(!collection)
{
res.status(404).json({message: 'No collection found'});
}else{
collection.movies.pop(req.body);
collection.save();
res.json(collection);
}
});
All it currently does is pop off the front object in the array, but I need it to remove the object in the array that is equal to req.body

Look into the $pull operator
collection.movies.pull(req.body);

You can remove an item in an array with $pull
Collection.update({
_id: req.params.id
}, {
$pull: {
'movies': req.body
}
}, (err, collection) => {
if (err) {
res.status(400).json(err);
}
console.log(res);
res.status(200);
});

Related

MongoDB / NodeJS can't push to array

I am trying to add tags to existing tags in a MongoDB collection with this Schema:
const workSchema = new mongoose.Schema({
title: {
type: String,
required: "Tile can't be blank"
},
description: {
type: String
},
imageURL: {
type: String,
unique: true
},
workURL:{
type: String,
unique: true
},
tags:{
type:Array
},
createdDate: {
type: Date,
default: Date.now
}
});
const Work = mongoose.model('Work', workSchema);
module.exports = Work;
I made an API that makes a PUT request to "/api/work/:workId/tags"
exports.updateTags = (req, res) =>{
try{
const newTags = req.body.tags.split(',');
newTags.forEach(tag => {
db.Work.update(
{"_id": req.params.workId},
{
$push:{
tags: tag
}
}
)
})
res.status(200).send({message : "tags updated"})
}
catch(error){
res.status(400).send(error)
}
}
request.body:
{
tags:"a,b,c"
}
The problem is that the array won't update with the new tag values
I searched for other ways to update in the docs and on the web but I didn't find any solutions.
You haven't defined _id in your workSchema so the type of _id would be ObjectId
But req.params.workId is probably a String, so querying an ObjectId with a String won't work.
So you should convert req.params.workId to ObjectId using mongoose.Types.ObjectId
{ "_id": mongoose.Types.ObjectId(req.params.workId) }
But you can improve your code a bit more by using .findByIdAndUpdate and $each operator
.findByIdAndUpdate will automatically convert your _id to ObjectId
You can use $each to $push multiple array elements at the same time without using .forEach
Work.findByIdAndUpdate(req.params.workId, {
$push: { "tags": { $each: newTags } }
})

Updating a doc based on the value of an array element in the doc and the _id of the doc itself

As the question states, I'm failing to get this update operation to work.
My scenario:
I have an Event, a Ticket and a TicketPurchase. The Event and TicketPurchase have Ticket arrays as properties.
What I need to achieve:
Update the validated property of a particular Ticket in the ticket array of the TicketPurchase from true/false.
Decrement the total_quantity property of the same ticket from 1. in the master Event table.(all tickets in a TicketPurchase are copies of tickets in the master Event table)
Almost all my experience as a programmer has been spent working with MySQL, so I am still very much a beginner in the NoSQL world.
What I have tried:
Checked the docs
Spent some time on S/O and this proved to be the most relevant answer, but I can't get this solution to work.
Interchanged my usages of id and _id, putting operators like $set in and out of ' ' marks, and all other similar configurations, nothing will give.
ticket.js
const TicketSchema = new Schema({
type : {type: String},
total_quantity : {type: Number},
price : {type: String},
limit_per_order: {type: Number},
start_date: {type: Date},
end_date: {type: Date},
description: {type: String},
validated: {type: String, default: 'false'}
});
event.js
const EventSchema = new Schema({
title: {type: String},
location: {type: String},
start_date: {type: Date},
start_time: {type: String},
end_date: {type: Date},
end_time: {type: String},
description: {type: String},
organizer_name: {type: String},
organizer_about: {type: String},
cover_picture: {type: String},
tickets: [{type: Schema.Types.ObjectId, ref: 'Ticket'}],
access_code: {type: String, default: shortid.generate}
});
ticketPurchase.js
const TicketPurchaseSchema = new Schema({
user: {type: Schema.Types.ObjectId, ref: 'User'},
event: {type: Schema.Types.ObjectId, ref: 'Event'},
tickets: [{type: Schema.Types.ObjectId, ref: 'Ticket'}],
time_stamp: {type: Date}
});
update.js
for(var x of ticketPurchase.tickets){
//console.log(x);
if(x.id === ticket_id && x.validated === 'false'){
console.log('ticket not validated. Update');
TicketPurchase.update(
{_id: ticket_purchase_id, 'tickets._id': ticket_id},
{'$set':{
'tickets.$.validated': 'true'
}},
function (err){
console.log('updated validated');
if(err){console.log(err);}
}
);
Event
.update({_id: event_id, "tickets._id": x.id},
{$inc : {"tickets.$.total_quantity" : -1}});
console.log('updated ticket.total_qty');
payload['success'] = 'true';
}else if(x.id === ticket_id && x.validated === 'true'){
console.log('ticket validated');
payload['success'] = 'false';
payload['message'] = 'Ticket already validated.';
}
}
At first, here is a short example of how you could avoid cycles and do everything with only mongoose methods:
TicketPurchase.findById(purchaseId, (err, ticketPurchase) => {
Ticket.update({ _id: { $in: ticketPurchase.tickets }, _id: ticket_id, validated: 'false' },
{ $set: { validated: 'true' }, $inc: { total_quantity: -1 } },
(err, outcome) => {
console.log(`The number of validated tickets for the Purchase #${purchaseId} is ${outcome.nModified}`);
});
});
Here it does everything in the same operation: finds the ticket which belongs to the purchase and isn't validated yet, thereafter it sets the validated property to 'true' and decrements the quantity. Finally, you'll get the total number of modified records.
In your example I can't see that you've populated your tickets in the purchaseTicket. To iterate through ticket objects in the manner you wrote, you'd be compelled to populate it for their parent entity since the property ticketPurchase.tickets contains only references. The expression would look like: TicketPurchase.findById(purchaseId).populate('tickets').then(purchase => { ... })
Where purchase has the property tickets filled up with its ticket objects instead of merely id objects.
Adding to that, you seem not to have to update tickets in the Event schema since you can do it directly in the Ticket schema (just like I did in my example overhead) since all the references eventually point out to their objects in the schema.

pushing data to embedded array error nodeJS

I'm using mongoose, and I'm getting an error while trying to push some data into an embedded array for one of my already established documents. My app is basically like a forum where a user post a topic question, someone can answer it, and then someone can post a comment on that answer. The problem is that, I've set up my database so that the topics and answers are referenced to one another in separate models, but the comments are embedded within the answer schema. With every method I try I get an error saying either:
message: 'The field \'comment\' must be an array but is of type Object in document {_id: ObjectId(\'5669acad9b68142c1258b472\')}',
driver: true,
index: 0,
code: 16837,
errmsg: 'The field \'comment\' must be an array but is of type Object in document {_id: ObjectId(\'5669acad9b68142c1258b472\')}' }
or:
{ [ValidationError: Answer validation failed]
message: 'Answer validation failed',
name: 'ValidationError',
errors:
{ 'comment.1._id':
{ [CastError: Cast to ObjectID failed for value "[object Object]" at path
"_id"]
message: 'Cast to ObjectID failed for value "[object Object]" at path "_
id"',
name: 'CastError',
kind: 'ObjectID',
value: [Object],
path: '_id',
reason: undefined } } }
Answer.js: model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var AnswerSchema = new Schema(
{
_topic: {type: Schema.Types.ObjectId, ref: 'Topic'},
_user: {type: Schema.Types.String, ref: 'User'},
likes: Number,
dislikes: Number,
content: String,
created_at: {type: Date, default: new Date},
comment: [{
_user: {type: Schema.Types.String, ref: 'User'},
content: String
}]
});
var Answer = mongoose.model('Answer', AnswerSchema);
topics.js: controller
add_comment: function(req,res)
{
Answer.findOne({_id: req.body.answerid}, function(err, answer)
{
if(err)
{
console.log(err)
}
else
{
console.log(answer);
answer.comment.push([{_user: req.body.currentUser, content: req.body.content}]);
answer.save(function(err)
{
if(err)
{
console.log(err);
}
else
{
res.redirect('/');
}
})
}
})
}
if i simply update the data (to replace other comments) it will process it with no err, if I use an operator like $push I'll receive the same errors.
Thanks for any help.

How to write a mongoose query to filter subdocuments

I have a Mongoose schema called "Users" which has a "Roles" subdocument as one of its variables like so:
var UserSchema = new mongoose.Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
roles: [ { type: Number, ref: 'Role' } ]
});
var RoleSchema = new mongoose.Schema({
_id: Number,
name: { type: String, required: true, unique: true },
description: { type: String, required: true }
});
I want to create a Mongoose query that will find all users with roles.names of "admin" or "owner". I've tried using this query, which I thought would work, but I don't get any users when I use the where...in part.
var roles = ["owner", "admin"];
User.find()
.populate('roles')
.where('roles.name').in(roles)
.sort({'_id': 1})
.exec(function (err, users) {
res.send(users);
});
Can someone tell me where my logic is wrong?
It's not possible to query more than one collection with a single query. If Role was an embedded subdocument in User then you could do a query on roles.name but presently that's not supported because it's a separate collection that is referenced in User.
However, a workaround is to add another filter step after the query returns that manually filters out documents which don't have any roles that matched the populate criteria but first you need to use a match query in populate instead of where method:
var roles = ["owner", "admin"];
User.find()
.populate('roles', null, { name: { $in: roles } } )
.sort({'_id': 1})
.exec(function (err, users) {
users = users.filter(function(user){
return user.roles.length;
});
res.send(users);
});
This is built into Mongoose populate() here . You can simply structure a query like this:
var roles = ["owner", "admin"];
User.find()
.populate({
path: 'roles',
match: { name: { $in: roles }},
select: 'name'
})
.sort({'_id': 1})
.exec(function (err, users) {
res.send(users);
});

Mongoose populate documents

I got 3 database models in mongoose that looks like this:
//profile.js
var ProfileSchema = new Schema({
username: { type: String, required: true },
password: { type: String, required: true },
matches: [{ type: Schema.Types.ObjectId, ref: 'Match' }]
});
//match.js
var MatchSchema = new Schema({
scores: [{ type: Schema.Types.ObjectId, ref: 'Score', required: true }],
});
//score.js
var ScoreSchema = new Schema({
score: {type: Number, required: true},
achivement: [{type: String, required: true}],
});
And I try to populate a profile with
Profile.findOne({ _id: mongoose.Types.ObjectId(profile_id) })
.populate('matches')
.populate('matches.scores')
.exec(function(err, profile) {
if (err) {...}
if (profile) {
console.log(profile);
}
});
The matches get populated but I dont get the scores in matches to populate. Is this not supported in mongoose or do I do something wrong? Populate gives me this:
{
user_token: "539b07397c045fc00efc8b84"
username: "username002"
sex: 0
country: "SE"
friends: []
-matches: [
-{
__v: 1
_id: "539eddf9eac17bb8185b950c"
-scores: [
"539ee1c876f274701e17c068"
"539ee1c876f274701e17c069"
"539ee1c876f274701e17c06a"
]
}
]
}
But I want to populate the score array in the match array. Can I do this?
Yes, you are right. I tried using Chaining of populate I got same output.
For your query please use async.js and then populate by the method mentioned below.
For more details please have a look at this code snippet. It is a working, tested, sample code according to your query. Please go through the commented code for better understanding in the code below and the link of the snippet provided.
//Find the profile and populate all matches related to that profile
Profile.findOne({
_id: mongoose.Types.ObjectId(profile_id)
})
.populate('matches')
.exec(function(err, profile) {
if (err) throw err;
//We got the profile and populated matches in Array Form
if (profile) {
// Now there are multiple Matches
// We want to fetch score of each Match
// for that particular profile
// For each match related to that profile
async.forEach(profile.matches, function(match) {
console.log(match, 'match')
// For each match related to that profile
// Populate score achieved by that person
Match.find({
_id:match.id
})
.populate('scores', 'score')
.exec(function (err, score) {
if (err) throw err;
// here is score of all the matches
// played by the person whose profile id
// is passed
console.log(score);
})
})
}
});
Profile.findOne({ _id: mongoose.Types.ObjectId(profile_id) })
.populate('matches.scores')
.exec(function(err, profile) {
if (err) {...}
if (profile) {
console.log(profile);
}
});

Categories

Resources