Mongoose: Populate path using field other than _id - javascript

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

Related

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

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.

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.

Preventing Duplicate ID Creation in MongoDB

In my Node/MongoDB backend I have a model that references a payers collection, like so:
clients: [{ id: { type: mongoose.Schema.Types.ObjectId, ref: 'clients' } }],
This is working, in that an id that's a reference to the correct "client" gets inserted. However, what's also happening is that mongo is auto-inserting a mongo ID. So in the document in question I end up with this:
clients: [{
id: 6b8702ad021ba27d4a3b26h9, // my correct ref object ID
_id: 4n8702bv036ba12g6a3b28f4 // an additional object ID auto inserted by mongo
}]
How do I prevent the auto insertion of the mongo ID in a situation like this? And, relatedly, if I were to use an _ in my original ref, like so:
clients: [{ _id: { type: mongoose.Schema.Types.ObjectId, ref: 'clients' } }],
...would this prevent this from happening to begin with, since there would already be a value for "_id"? In other words, would Mongo then NOT auto insert another ID? If so, that's the route I will take.
Yes, overwriting _id will work. Just be aware that _id is your database's primary key, so it needs to be unique or Mongo will throw an error.

Data structure for movie ticketing system in MongoDB

I am trying to make a booking system for a cinema with MongoDB.
I keep all the information about the movie in a model Movie, including title, description, actors, rating, director, etc.
However, a movie can be shown in the cinema in different rooms and at different dates, so I also have a model Showtime, where I have room, price, and date.
Finally, I also need a model Ticket with fields purchasedBy, purchasedAt, isRedeemed, etc.
However, I don't know how to link the models and extract the showtimes. I want a list on the frontpage with all the movies (with title, description and image), but I also want to show the date and price. The problem is that date and price can vary since each movie can have multiple (and different) dates and prices, so I just want to show the smallest price and soonest date.
At the moment, I have a schema that looks like something like
movieSchema = Schema({
name: String,
description: String,
image: String,
showtimes: [{ type: ObjectId, ref: 'Showtime' }]
});
I could get a date and price by just taking the first showtime in the array of showtimes
Movie.find().populate('showtimes').then(movies => {
movies.forEach(movie => {
console.log(movie.showtimes[0].date)
console.log(movie.showtimes[0].price)
});
});
However, I need to sort by the most recent date and/or the lowest price, so I am not sure if my data structure is appropriate for this purpose.
What would be ideal, would be to be able to do something like this:
Movie.find().sort('showtimes.date showtimes.price').populate('showtimes').then(movies => {
...
});
but since I am only storing the IDs of the showtimes in my showtimes field, this is not possible.
Alternatively, I could change the schema to
showtimeSchema = Schema({
date: Date,
price: Number
});
movieSchema = Schema({
name: String,
description: String,
image: String,
showtimes: [showtimeSchema]
});
so I don't have to use populate(). However, the problem is that when a customer buys a ticket, I need to refer to the showtime in the ticket object, so I need a model for showtimes on its own.
Edit
As mentioned in the comments, it might be clever to embed the documents directly in movieSchema. However, I don't know what my Ticket model should look like.
Right now it is something like
ticketSchema = Schema({
showtime: { type: ObjectId, ref: 'Showtime' }
purchasedAt: Date,
purchasedBy: { type: ObjectId, ref: 'User' }
isRedeemed: Boolean
})
So when I am printing the ticket, I have to do something like
Ticket.findById(ticketId).populate({
path: 'Showtime',
populate: {
path: 'Movie'
}
}).then(ticket => {
console.log(ticket.date);
console.log(ticket.event.name);
});
I would use your second schema; there's really no sense in creating a new model/collection for the showtimes since you won't be making transactions on the showtime, but on visitors, movies, and tickets. So that looks like this:
movieSchema = Schema({
name: String,
description: String,
image: String,
showtimes: [{
date: Date,
price: Number
}]
});
Then, what you can do is sort by the min/max values of the array. So that would look something like this:
Movie.find().sort({'name.showtimes.date' : -1, price: 1})
This takes the latest showtimes for each movie and sorts by that time (as well as lowest price).
EDIT:
You could have a reference to the movie in the ticket, and store the showtime there as well:
ticketSchema = Schema({
showtime: Date,
purchasedAt: Date,
purchasedBy: { type: ObjectId, ref: 'User' }
isRedeemed: Boolean,
movie: { type: ObjectId, ref: 'Movie' }
})
If you need more structure than that for whatever reason, I would look at using SQL instead. Nested populates (essentially SQL JOINs) are a maintenance/optimization nightmare, and RDBMS are more suited for data like that.
EDIT 2:
Ok, let's weigh our options here. You are right, in the event of a time/venue change, you would have to update all tickets. So storing the showtime separately gives us that benefit. On the other hand, this now adds a layer of complexity to virtually every single ticket you look up, not to mention the performance detriment and added server costs. Even if ticket/venue changes happen frequently, I'm almost positive that your ticket lookups are much more frequent.
That being said, I think a good approach here is to store an _id on the showtime subojects, and lookup your tickets that way:
showtimeSchema = Schema({
date: Date,
price: Number
});
movieSchema = Schema({
name: String,
description: String,
image: String,
// When you use a sub-schema like this, mongoose creates
// an `_id` for your objects.
showtimes: [showtimeSchema]
});
// Now you can search movies by showtime `_id`.
Movie.find({showtimes: 'some showtime id'}).exec()
You could go one step farther here and register a static on the Movie model for easy lookup by showtime _id:
Movie.findByShowtime('some showtime id').exec()
When you've fetched the movie, you can grab the showtime like this:
var st = movie.showtimes.id('some showtime id');
Further reading on subdocuments.

Saving document and its object id for referencing in other documents

In a Node.js App with Mongodb/Mongoose. i have two collections. Users and Books. My question is: when a user saves a book i have to save it in the Books collection and its object id inside Users collection for referencing. Two save operation for one user input.
Is this correct way? is this Mongodb standard? in a relation based database system its a wrong architecture but in the lack of Join in Mongodb what should i do? if i have a large database should i save current books id inside each of related collections?
I know that i can has books embedded into each user document but it has own problems.
Im confused. what should i do?
You can create a MySQL "join" in MongoDB with Mongoose. It is not the MongoDB standard but it makes developing in MongoDB sometimes a lot easier. Below are two simple example schema's.
var BookSchema = new Schema({
title: {
type String
},
author: {
type: String
}
});
module.exports = mongoose.model('Book', BookSchema);
var UserSchema = new Schema({
username: {
type: String,
required: true
},
books: [{
type: String,
ref: 'Book'
}]
});
module.exports = mongoose.model('User', UserSchema);
In the last schema there is a "books" property with a reference to books. You can store Strings of Object id's inside the array (not as an object). If you want, you can also store Object id's instead of Strings. Below is an example schema for a user:
{
username: "Fcoder",
books: ["550adf3899fbe92a168d3051", "550adf3899fbe92a168d3052"]
}
When querying your MongoDB database, you can populate the data. Your query will look like this:
User.find({}).populate('books').exec(function(err, data) {
// callback
});
Inside data, you will find something like this:
{
username: "Fcoder",
books: [{
_id: ObjectId("550adf3899fbe92a168d3051"),
title 'Some title 1',
author: 'Some author 1'
}, {
_id: ObjectId("550adf3899fbe92a168d3052"),
title 'Some title 2',
author: 'Some author 2'
}
}

Categories

Resources