remove nested object references mongoose - mongodb

I have several mongoose models in my application - assignment, submisssion, and file.
var assignmentSchema = new Schema({
submissions:[{
type: mongoose.Schema.Types.ObjectId,
ref: 'Submission'
}]
});
var submissionSchema = new Schema({
assignment: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Assignment'
},
files: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'File'
}],
})
var fileSchema = new Schema({
submission: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Submission'
}
})
I defined a remove hook for assignments, so when an assignment is removed from my mongodb, all submissions are deleted too.
assignmentSchema.pre('remove', function (next) {
var assignment = this;
assignment.model('Class').update(
{_id: {$in: assignment.classes}},
{$pull: {assignments: assignment._id}},
{multi: true},
next
);
//will delete all submissions that were in the assignment
assignment.model('Submission').remove(
{_id: {$in: assignment.submissions}}).exec();
next
});
It works ok, but I also want to remove all files that are related to deleted submissions. How can I achieve this?

You have to setup a pre remove hook for your submission documents.
Then in your assignmentSchema loop through the assignment.submissions array, find each assignment document and execute a remove on that document.
Here is an example. Not tested.
assignmentSchema.pre('remove', function(nextAction) {
var assignment = this;
assignment.model('Class').update({
_id: {
$in: assignment.classes
}
}, {
$pull: {
assignments: assignment._id
}
}, {
multi: true
},
next
);
Async.each(assignment.submissions, function(subId, next) {
submissionSchema.findById(subId).exec(function(err, submission) {
submission.remove();
next();
})
}, function() {
nextAction();
})
});
submissionSchema.pre('remove', function (next) {
///remove files here.
})

Related

MongoDB - remove many from arrays of all existing elements

I have some simple user data. Here is example for one user:
const userSchema = new Schema({
userName: {
type: String,
},
projectsInput: [
{
type: Schema.Types.ObjectId,
ref: "Project",
},
],
projectsHold: [
{
type: Schema.Types.ObjectId,
ref: "Project",
},
],
});
I want by having ProjectId to be able to remove all records from all users that contains it.
if I get the first one
60f02d21159c4b4110f21a32
how I can perform updateMany function for my UserModel?
return UserModel.updateMany(
{
projectsInput: {
$elemMatch: args.projectId,
},
},
{
projectsInput: {
$slice: [projectsInput.$, 1],
},
}
);
})
Here is my code that is not working.
args.projectId = 60f02d21159c4b4110f21a32 (my id for the project I want to delete)
and UserModel is my mongodb Schema for user.
you can use $pull
{
$pull: {
projectsInputs: "123"
}
}

GraphQL Mutation Updating Users Followers with Mongoose/MongodDB - $set is empty error

I have this mutation set up:
followUser: {
type: UserType,
args: {
_id: { type: GraphQLString },
firebaseUid: { type: GraphQLString },
following: { type: new GraphQLList(GraphQLString)},
},
resolve(parentValue, { firebaseUid, _id, following}) {
const update = {
$set: { "following": [firebaseUid] },
$push: { "following": { firebaseUid } }
}
return UserSchema.findOneAndUpdate(
{ _id },
update,
{new: true, upsert: true}
)
}
},
I'm trying to add new followers into my graphql user's collection. My user model:
const UserSchema = new Schema(
{
firebaseUid: String,
following: [{ type: Schema.Types.ObjectId, ref: 'User' }],
followers: [{ type: Schema.Types.ObjectId, ref: 'User' }],
},
{ timestamps: true }
);
module.exports = mongoose.model("User", UserSchema);
So at first, the user doesn't have any followers, so it won't have that field yet. When user adds someone to their friends list, thats when the field will appear in mongodb. Right now I'm getting this error:
"message": "'$set' is empty. You must specify a field like so: {$set: {<field>: ...}}",
I'm not sure if I'm doing the $set correctly.
The UserType
const UserType = new GraphQLObjectType({
name: "User",
fields: () => ({
_id: { type: GraphQLString },
firebaseUid: { type: GraphQLString },
following: { type: new GraphQLList(GraphQLString) },
followers: { type: new GraphQLList(GraphQLString) },
...
})
});
edit:
current mongodb data collection:
_id: ObjectId("5e5c24111c9d4400006d0001")
name: "Mr. Smith"
username: "mrsmith"
after running the update
_id: ObjectId("5e5c24111c9d4400006d0001")
name: "Mr. Smith"
username: "mrsmith"
following: ["fdsaduybfeaf323dfa"] // <-- this gets added
Currently mongooses validator is rejecting the update. To fix this you need the following:
You only need to $push since it will automatically create an array if the property does not exist
You should remove the extra { } around the firebaseUid in the $push because otherwise the following array will contain objects with a firebaseUid property instead of directly containing the Uid (or would if the schema validator allowed it)
Mongo ObjectIds can only be converted from strings when they are 12-byte hexadecimal, and firebaseUid is not, so the schema should be typed to String instead of ObjectId as the validator will reject the field for update otherwise.

mongoose .findById & .find return object & array, causes compatibility issue

When the user visits a certain page of my App, the Component dispatches an action to fetch information. Namely, the action performs the following operations:
Base.find({req.params.id})
BaseUser.find({ baseId: req.params.id }) **returns multiple docs**
Message.find({ baseId: req.params.id }) **returns multiple docs**
The operation happens in this order. I could query the first one via .findById, but for uniformity of the problem I chose .find(). The problem now is that the results of
Promise.all([
Base.find({ _id: req.params.id }),
BaseUser.find({ baseId: req.params.id }),
Message.find({ baseId: req.params.id })
])
come in an array, like so:
[
[ { created: 2018-08-29T23:59:35.380Z,
_id: 5b8741151985662f10d04fdb,
creatorId: 5b86f7970cd98b2004969bf0,
title: 'testBase1',
} ],
[ { created: 2018-08-30T00:57:57.764Z,
acceptedMembership: true,
isCreator: true,
_id: 5b8741151985662f10d04fdc,
userId: 'tester1',
baseId: 5b8741151985662f10d04fdb }
],
[ { created: 2018-08-30T00:58:09.182Z,
_id: 5b8741211985662f10d04fdd,
baseId: 5b8741151985662f10d04fdb,
content: 'testMessage1' }
]
]
This quite obviously causes problems when further trying to map/filter/res.json() the data. Is there any known way to return this in a single array, or even better, pass it to the front-end (redux action) as an object? Does anyone know of a better solution which handles this problem slightly differently, and prevents me from fetching each of those methods on subcomponents ?
update:
I have now constructed this, which is fairly ugly to look at:
let completeObject = {
base: {},
users: [],
messages: []
};
Base.findById(req.params.id)
.then(data => {
completeObject.base = data;
return data;
})
.then(data => {
BaseUser.find({ baseId: req.params.id })
.then(data => {
completeObject.users = data;
return data;
})
.then(data => {
Message.find({ baseId: req.params.id }).then(data => {
completeObject.messages = data;
return res.json(completeObject);
});
});
})
Why don't you setup ref in the Base model to the BaseUser and Message and then use populate to fill those arrays and get one object as result filled with the arrays of BaseUser and Message?
From what I see you key on the req.params.id which means you have a cross-reference between those collections anyway.
Here is an example:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var BaseSchema = Schema({
_id: Schema.Types.ObjectId,
creatorId: Schema.Types.ObjectId,
title: String,
users: [{ type: Schema.Types.ObjectId, ref: 'User' }],
messages: [{ type: Schema.Types.ObjectId, ref: 'Message' }],
});
var UserSchema = Schema({
_id: Schema.Types.ObjectId,
acceptedMembership: Boolean,
isCreator: Boolean,
userId: String,
baseId: Schema.Types.ObjectId
});
var MessageSchema = Schema({
_id: Schema.Types.ObjectId,
baseId: Schema.Types.ObjectId,
content: String
});
var Base = mongoose.model('Base', BaseSchema);
var User = mongoose.model('User', UserSchema);
var Message = mongoose.model('Message', MessageSchema);
Now that the schemas are defined (and you added some records) you could find a Base by _id and populate users and messages:
Base.
findOne({ _id: req.params.id }).
populate('users').
populate('messages').
exec(function (err, base) {
if (err) return handleError(err);
console.log(base);
});
You should check the mongoose documentation on how to save / populate references etc.

Mongoose pull ObjectId from array

i'm trying to do a pretty simple operation, pull an item from an array with Mongoose on a Mongo database like so:
User.update({ _id: fromUserId }, { $pull: { linkedUsers: [idToDelete] } });
fromUserId & idToDelete are both Objects Ids.
The schema for Users goes like this:
var UserSchema = new Schema({
groups: [],
linkedUsers: [],
name: { type: String, required: true, index: { unique: true } }
});
linkedUsers is an array that only receives Ids of other users.
I've tried this as well:
User.findOne({ _id: fromUserId }, function(err, user) {
user.linkedUsers.pull(idToDelete);
user.save();
});
But with no luck.
The second option seem to almost work when i console the lenghts of the array at different positions but after calling save and checking, the length is still at 36:
User.findOne({ _id: fromUserId }, function(err, user) {
console.log(user.linkedUsers.length); // returns 36
user.linkedUsers.pull(idToDelete);
console.log(user.linkedUsers.length); // returns 35
user.save();
});
So it looks like i'm close but still, no luck. Both Ids are sent via the frontend side of the app.
I'm running those versions:
"mongodb": "^2.2.29",
"mongoose": "^5.0.7",
Thanks in advance.
You need to explicitly define the types in your schema definition i.e.
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
linkedUsers: [{ type: Schema.Types.ObjectId, ref: 'User' }]
and then use either
User.findOneAndUpdate(
{ _id: fromUserId },
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
or
User.findByIdAndUpdate(fromUserId,
{ $pullAll: { linkedUsers: [idToDelete] } },
{ new: true },
function(err, data) {}
);
I had a similar issue. I wanted to delete an object from an array, using the default _id from mongo, but my query was wrong:
const update = { $pull: { cities: cityId }};
It should be:
const update = { $pull: { cities: {_id: cityId} }};

Mongoosejs populate and aggregate subdocuments

I have a '3-layered' relationship in MongooseJS like so, it's two one-to-many relationships between subdocuments. Like so:
var BroadcastSchema = new Schema({
...
_donationAddresses: [{
type: Schema.Types.ObjectId,
ref: 'DonationAddress'
}]
});
var DonationAddressSchema = new Schema({
...
_donations: [{
type: Schema.Types.ObjectId,
ref: 'Donation'
}]
});
var DonationSchema = new Schema({
...
amount: Number
});
I want to get the $sum total of the amount:Number on the DonationSchema
So far I've populated the Donation by using a work-around listed here (because as far as I know you can't populate a populate so far as I know)
Broadcast.find()
.exec(function(err, broadcasts) {
// this works
var iter = function(broadcast, callback) {
DonationAddress.populate(broadcast._donationAddresses, {
path: '_donations'
}, callback);
};
// tried to iterate over the donation address and
// aggregate the _donations.amount
var iter2 = function(broadcast, callback) {
DonationAddress.aggregate([{
$match: {
_id: broadcast._donationAddresses
}
}, {
$unwind: "$_donations"
}, {
$group: {
_id: "$_id",
total: {
$sum: "$_donations.amount"
}
}
}], callback);
};
async.each(broadcasts, iter, function done(err) {
async.each(broadcasts, iter2, function done(err) {
res.json(broadcasts);
});
});