Ratings not updating as expected in Mongo - javascript

I'm trying to implement a rating system along the lines of upvotes/downvotes.
Users can vote on a lesson only once. They can switch their votes between up and down. Voting the same as their previous vote removes their vote.
I'm trying to accomplish this with pull() but it empties out the entire ratings array including other users' votes.
Rating Schema
var RatingSchema = new Schema({
rating: Boolean,
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
Lesson Schema
var LessonSchema = new Schema({
...,
ratings: [RatingSchema]
});
Problem code
//assuming lesson.ratings looks like this
[{user: 123..., rating: true},
{user: 321..., rating: true}];
//assuming lesson was loaded from a query
lesson.ratings.pull({user: 123...});
//resulting ratings
[]
I don't if this is expected behavior but I just want to remove the matching rating and not all of the sub docs.

Found a working sol'n.
The problem was the way the rating schema was originally defined and how save() works:
OG Rating Schema
var Ratings = new Schema({
_id: false,
rating: Boolean,
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
I thought the user ref would be unique enough to work with wrt modifying ratings.
I still don't know why the pull() would delete all subdocs regardless of user id.
When its time to save, because of the lack of _id, it updated the entire ratings array overwriting altogether.
To fix this instead of using the user to update, I switched it to use _id.
Also, careful when switching the _id flag on and off again. The driver will throw undefined objectid errs.

Related

Mongoose: Reference schema in properties or add as an array? which is better

For example if i have two schemas User and Post , should i add User reference in Post's properties, or add Post schema as an array inside User Schema? which is better performance wise( and other aspects).
var PostSchema = new mongoose.Schema({
title: String,
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
});
OR
var UserSchema = new mongoose.Schema({
name: String,
posts: [PostSchema]
});
var PostSchema = new mongoose.Schema({
title: String,
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
});
I think this way is better than others.
You should have a reference to the user in the PostSchema.
This is the better approach because a user can have multiple posts and if you save the posts in the UserSchema as an array, that array can grow infinitely. This can cause problems because there's a limit on the max size of a mongodb document. Single mongodb document cannot be greater than 16Mb.
Saving Posts in the User schema is better from the performance perspective but considering the limit on the size of mongodb document and the fact that a user can have many posts, a child document (Post) referencing its parent document (User) is the better approach here.

Duplicate key error while using _id from another collection

I have 2 collections so far.
The first is a 'Users' collection (That works well), and the other is a 'Rooms' collection (both created for a chat application).
I want every room to have a "users" array that will contain the user._id of every user that is in that room,
meaning I should be able to put the same user._id (from the user collection) in every one of the rooms right?
After creating a room successfully with 2 user._ids in the "users" array,
I tried making another one using one of the user._ids I used in the first room.
Then I got this error:
MongoError: E11000 duplicate key error collection: ratchat.rooms index: users_1 dup key:
{ users: "5fe08d452f34530e641d8f8c" }
After checking with a debugger I've found that the error occurs only when I use a user._id that is already used in another room's "users" array.
The only thing I could think of that could cause this problem is the Room schema,
maybe there's something I missed while reading the docs...
At first my Room schema looked like this:
const roomSchema = new mongoose.Schema({
users: [String],
hasLeft: [String],
isGroup: { type: Boolean, default: false },
},
});
const Room = mongoose.model("Room", roomSchema);
Then I thought maybe mongoDB needs to know that the ObjectIds that are in the users array are just references to another collection:
const roomSchema = new mongoose.Schema({
users: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
hasLeft: [String],
isGroup: { type: Boolean, default: false },
},
});
const Room = mongoose.model("Room", roomSchema);
No luck so far...
{autoIndex: false}
After research, I have found the reason for this error:
mongoose automatically creates indexes,
this is not only a duplicate error issue, but can cause a significant performance impact later in production.
According to mongoose docs, you can easily disable this behavior by setting the autoIndex option of your schema to false, or globally on the connection by setting the option autoIndex to false.
mongoose.connect('mongodb://user:pass#localhost:port/database', { autoIndex: false });
// or
mongoose.createConnection('mongodb://user:pass#localhost:port/database', { autoIndex: false });
// or
animalSchema.set('autoIndex', false);
// or
new Schema({..}, { autoIndex: false });
Don't forget to drop the entire collection before trying again
Because the collection is already indexed, emptying it completely won't work.
You have to drop the entire collection.

Mongoose how to update array from child to parent

I have following schema for Audio.
const AudioSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
uploaderId: {
type: String,
required: true
}
});
Instead of referencing the User, I just store the User's _id as uploaderId.
In my User schema I also have audioFiles: [Audio] array for all audio files that user has uploaded.
const UserSchema = new mongoose.Schema({
...,
audioFiles: [Audio]
});
When I try to update my AudioSchema from my REST Api, I can change all the properties and that works, but after saving this Audio model those changes doesn't affect the User model.
Now I created a new branch and try to change uploaderId to UserSchema. But I wonder is there a solution for this without referencing the UserSchema
I managed to do this with help of MongooseArray.prototype.pull method.
Steps for solving this problem is really easy.
First I get the User that is associated with AudioModel.uploaderId, then I used the user.audioFiles.pull() method. Correct code is below.
let user = await UserService.getUser(userId);
await user.audioFiles.pull({
_id: audioId //audioId is the id which i'm trying to remove from array
});
await user.save();
I also added try-catch block to handle errors.
Anyone having this kind of issue can use the link below to get more information about MongooseArray.prototype.pull method.
Also you can check the other answers in this post.

Mongoose Populate - how can you dynamically or programmatic perform field selection

I have a query 1 which return an array of records, let's say activityList from activitySchema. Now On this activityList array, I am trying to use populate to populate user Information. I would like to dynamically select fields based on the certain activity type. Here is my schema since each sub-document has many fields.
var activitySchema = new Schema({
userId: {type: Schema.Types.ObjectId, ref: 'users'}
, updateDt: {type: Date, Default: Date.now()}
, activityType: String
, weight: Number
, activityAttributes: [String]
});
users schema has many sub-documents like
var userSchema = new Schema({
auth: authSchema
, membership: membershipSchema
, personal: personalSchema
, location: locationSchema
, religion: religionSchema});
based on the activity type value let's say "PERSONAL", "LOCATION" & "RELIGION" populate should only return those values.
Sorry, no can do with the schema you have.
You can query the activitySchema object, examine the activityType, then query the userSchema selecting only the fields you want based on the activity type: PERSONAL, LOCATION, RELIGION. So you can proceed with two queries instead of one.
You could also grab them all with populate and then filter out the props you don't want in javascript. You would still need to send all of that data over the network to your calling code. But maybe you save some bandwidth if the above code shunts the data someplace else (like a browser).
Without knowing more about what you are trying to accomplish I can't advise you on a better way to model your data. Potentially breaking up the userSchema is probably something to consider. There's no doubt that if you put in the time you can have a data model that gets you everything in one shot.

group and collect document based on field in mongodb

I have notifications collection with this schema
_const notificationsSchema = new Schema({
_from: {
type: Schema.ObjectId, ref: 'User',
},
_to: {
type: Schema.ObjectId, ref: 'User',
},
_target: String,
type: {
type: String,
},
createdAt: {
type: Date,
default: Date.now,
},
});
How to group notifications like facebook " and 50 others liked your post"?
How to retrieve this shape of data, for the documents that has same _parent?
[
{
_id: "",
_to: "",
_parent: "",
lastThreeUsers: [_ from, ...],
count: 50
}
]
This is a very open ended response. There is no one right answer. I have provided both a naive and ideal approach to the problem. You may think of another, better way.
You would need to have a something along the lines of a "Likes" collection where you store all likes from users where each like has a reference to the post being liked. Then you would have to perform an aggregation on the likes collection to get all the likes where the postId is equal to the id of the post you want total likes for.
First attempt that.
Then a better, "real world" approach is to also add a "likesCount" (call it what you want) property onto Post documents that keeps tracks of the likes on the post. Update the likesCount each time a user likes/dislikes that post. This way you won't actually have to search through the "Likes" collection over and over again everytime your have to show that data.

Categories