MongoDB: How can populate reference, and delete element in array after based on the ID of the reference - javascript

So I have a situation where I need to delete elements in an array of reference / ObjectIds, but the delete condition will be based on a field in the reference.
For example, I have schemas like the following:
const UserSchema = new mongoose.Schema({
firstName: String,
lastName: String,
homeFeeds:[{type: Schema.Types.ObjectId, requried: true, ref: "Activity"}];
}); // User , is the referenece name
const ActivitySchema = new mongoose.Schema({
requester: {type: Schema.Types.ObjectId, requried: true, ref: "User"},
message: String,
recipient: {type: Schema.Types.ObjectId, requried: true, ref: "User"},
}) // Activity, is the reference name
Now I need to delete some of the homeFeeds for a user, and the ones that should be deleted need to be by certain requester. That'll require the homeFeeds (array of 'Activity's) field to be populated first, and then update it with the $pull operator, with a condition that the Activity requester matches a certain user.
I do not want to read the data first and do the filtering in Nodejs/backend code, since the array can be very long.
Ideally I need something like:
await User.find({_id: ID})
.populate("homeFeeds", "requester")
.updateMany({
$pull: {
homeFeeds.requester: ID
}
});
But it does not work, Id really appreciate if anyone can help me out with this?
Thanks

MongoDB doesn't support $lookup in update as of version v6.0.1.
MongoServerError: $lookup is not allowed to be used within an update.
Though, this doesn't have to do with Mongoose's populate as populate doesn't depend on $lookup and fires additional queries to get the results. Have a look at here. Therefore, even if, you could achieve what you intend, that is avoiding fetching a large array on nodejs/backend, using mongoose will do the same thing for you behind the scenes which defeats your purpose.
However you should raise an issue at Mongoose's official github page and expect a response.

Related

Mongoose, $pull an element from nested array and update document based on the presence of the element

I am working on a mongoose schema similar to this:
const actionSchema = {
actions: {
type: [{
actionName: {
type: String,
required: true
},
count: {
type: Number,
default: 0,
required: true
},
users: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
}]
}};
It is a nested schema of a post schema.
Here, actions are dynamically generated and number of people does that action are maintained by count and their identity is maintained by users array.
As you see, actions is an array of objects which further contain users array.
I want to check if a provided user id is present in any of the action object and then remove it from array and also reduce the count.
Being totally new to mongoose and mongodb, one simple way I see is to find the post using Post.findById() which has to be updated, run js loops, update the post and call .save(). But it can be very costly when users array has thousands of user ids.
I tried .update() but can't understand how to use it in this case.
How about adding a method to the Post Model (like postSchema.methods.removeUserAction)? This gives access to document from this and allows to update the document and thus call .save(). Does it loads the full document to the client node application?
So please suggest the right way.
Thank you.
You should simplify your model, for example
// Model - Actions Model
const actionSchema = {
actionName: {
type: String,
required: true
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
};
And you can easily get the total actions via Model.count(), get specific action count with Model.count({ actionName: 'action name'}), and removing entries with Model.delete(condition). Unless there's a reason why you have it modeled this way.

Mongoose: Populate path using field other than _id

By default mongoose/mongo will populate a path using the _id field, and by it seems like there is no way to change the _id to something else.
Here are my two models which are connected with one-to-many relationship:
const playlistSchema = new mongoose.Schema({
externalId: String,
title: String,
videos: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Video',
}],
});
const videoSchema = new mongoose.Schema({
externalId: String,
title: String,
});
Normally, when querying a playlist you would populate videos just with .populate('videos'), but in my case I would like to use the externalId field instead of the default _id. Is that possible?
As far as I know, the way to achieve this with mongoose presently is by using virtuals. When populating virtuals, you can specify the localField and foreignField to whatever you want, so you are no longer bound to the default _id as foreignField. More details about this here.
For the scenario described in your question, you would need to add a virtual to the playerlistSchema, something like this:
playlistSchema.virtual('videoList', {
ref: 'Video', // The model to use
localField: 'videos', // The field in playerListSchema
foreignField: 'externalId', // The field on videoSchema. This can be whatever you want.
});
Now, whenever you query for player lists, you can populate the videoList virtual to get the referenced video documents.
PlaylistModel
.findOne({
// ... whatever your find query needs to be
})
.populate('videoList')
.exec(function (error, playList) {
/* if a playList document is returned */
playList.videoList; // The would be the populated array of videos
})

Users schema with poking other users schema in node.js

I am making an application in which a user can poke other users. Here is the code for the schema designs I have considered. The first is using only a users schema:
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Users' ,default:null}],
});
Another way is using a pokes schema. Here I'm storing the object ids of the pokes schema in the users schema.
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Pokes' ,default:null}],
});
In the third way I totally remove the relation between the two schemas:
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
});
In the second and third ways I can query for pokes easily.
I want to know which of the three is the best design and why. Also if userA pokes userB then it can be the case that userB can also poke back to userA. I'm learning node.js currently and am confused about the design in mongoDb.
Alright, so here's the best I can do. You briefly answered my question in my comment above but I'd like to point out it's important to think about what you are doing (or expect to be doing) more and how much more. That aside though, let's take a look at each schema.
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Users' ,default:null}],
});
When we look at this first one it seems inadequate for your needs. It's a collection of users who have a name and an array of pokes they have made. If we need to know who a user has poked then that's a really easy and fast query - it's right there under their name, search by name or _id and we're done! But what happens when you want to look up who has poked this user? You will need to query every single user and then search through every single pokes array for the original user. If we have m-many users and each has n-many pokes, that's doing m* n tests. Yikes. If m and n get big that's going to be a lot (think of the difference of 100 * 100 vs 10,000 * 10,000 or even more!). Even if you personally are not coding that search in your node then mongo is doing that search. So unless you're sure that looking up who has poked a user is going to be something that is pretty rare this is probably not a good option. Moving on:
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
pokes: [{ type : Schema.Types.ObjectId, ref: 'Pokes' ,default:null}],
});
Now we have a pokes schema, nice! If we wanted to do the search we discussed above we can instead query pokes directly based on to_user_id, and then if we need a name of all the users who initiated the pokes we can just query the users based on its _id. Not bad! We also still have the fast way to get the reverse, aka search for pokes a user has initiated, because there is still pokes in our user schema. What happens when a poke occurs, though? We have to update both schemas. So not only will we do a (relatively easy) insert into pokes, we will have to also update our pokes array of the user who did the poking. This might not be so bad, but what happens if one update fails? Now our data is inconsistent - users and pokes don't match. We're also doubling our updates every poke. This might not be a big deal, and if we're getting a user's pokes much more than we're poking then it might be an ok trade-off, but it becomes a little riskier because we've introduced somewhere we can be inconsistent. Alright, last one:
const pokesSchema = new Schema({
from_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
to_user_id: { type : Schema.Types.ObjectId, ref: 'Users' ,default:null},
});
const userSchema = new Schema({
name: { type : String},
});
First, note that these schemas are still related - the pokes schema reference users. It's just not doubly-related like the last one. Anyway, now we've removed the pokes array from the user schema. Ok! We don't run the risk of having inconsistent data anymore, noice! We're also not doing two updates for every poke, toit! The trade-off is now when we want to get the list of users a user has poked we have to do a similar query to the one we did above when we wanted to get a list of users a user has been poked by. Which isn't so bad, but is certainly not as fast as having the pokes array already sitting there and waiting.
In my opinion, unless you're searching for who users have poked (and not been poked by) significantly more often than doing anything else this third scenario is best. The schemas make logical sense, you're not updating twice. It's what I would use. But as I said, it's very important to consider your particular need and design.
Hope that helps!

Unique Fields in Mongoose

I'm trying to enforce uniqueness by field on a mongoose model, in an existing database.
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
}
})
Then when I create a user with an email that is already assigned, it passes, and I have 2 users with the same email.
I have read all related SO answers > 2015 and all about dropDups, which is deprecated.
I think I fixed this issue by manually running
db.users.createIndex({email:1}, {unique:true})
However this obviously becomes cumbersome both in development and in production, especially considering the mongoose docs state that the attribute takes care of it:
unique: boolean, whether to define a unique index on this property.
Can someone provide a clear solution to enforcing uniqueness by field on a Mongoose model, considering an existing database and a fresh collection? Thanks
Two user has been created before unique index has been created.So you can insert two users with same email.You can try code below, it make sure all index has been created:
UserModel.on('index', function(error) {
const user1 = new UserModel({
email: '3489'
});
const user2 = new UserModel({
email: '3489'
});
user1.save(console.log);
user2 .save(console.log);
});

Use Date as the ID in MongoDB?

Intending to log use of the API (user/route/params/time) in a Heroku/Node/Express/Mongodb web app, to allow various analytics (who/what/when/how often). One way I can think of is to push those to MongoDB.
Mongo will generate an ID automatically, and I see that it's possible to extract the created time from the autogenerated ID, but since the time stamp is all I want, now I wonder if I can use the date as the ID?
This seems to work, and the timestamps seem granular enough ("_id" : ISODate("2012-11-30T21:18:24.484Z")) that they'll be unique. Is this okay or just asking for an "ID not unique" error just when things get going?
var apilogSchema = new mongoose.Schema({
_id: {type: Date, default: Date.now},
userId: {type: mongoose.Schema.Types.ObjectId, required: false},
route: {type: String, required: false}
})
Get date and time from mongodb document _id field
what johnny said, wouldnt recommend that. especially when you are using it for logging user actions. (it is possible that 2 users do an action at the same millisecond, and now your id isnt unique anymore)
check out node-uuid if you are on node.js and want your id to contain a timestamp

Categories

Resources