duplicate key error when creating a new mongoose sub-document - javascript

When creating new document and then try to upsert a new sub-document I get this error:
Object {error: "E11000 duplicate key error index: sales.users.$gro…p key:
{ : ObjectId('537b7788da19c4601d061d04') }"}
error: "E11000 duplicate key error index: sales.users.$groups.groupId_1
dup key: { : ObjectId('537b7788da19c4601d061d04') }"
__proto__: Object
The sub-document I'm trying to insert is defined as sub-schema that has a groupId field with the requirements {unique: true}, {sparse: true}. The mongoose method call I'm using to do the upsert is:
User.findByIdAndUpdate(userId,
{ $push: { 'groups': userUpdate} },
function (err, obj) where userUpdate = { groupId: groupId }.
After dropping the indexes the problem is fixed and this error no longer occurs.
var UserSchema = new Schema({
email: {
type: String,
required: true,
unique: true
},
active: {
type: Boolean,
default: true
},
username: {
type: String,
required: true,
unique: true
},
password: {
salt: {
type: String,
required: true
},
hash: {
type: String,
required: true
}
},
securityQuestion: {
question: String,
salt: String,
hash: String
},
mobile: {
PIN: Number,
Number: Number
},
createDate: {
type: Date,
default: Date.now
},
updateDate: Date,
lastLoginDate: Date,
prevLoginDate: Date,
passChangeDate: Date,
locked: Boolean,
lockDate: Date,
failedCount: Number,
failedDate: Date,
profile: profile,
preference: preference,
courses: [UserCourseSchema],
groups: [UserGroupSchema],
rewards: [UserRewardSchema],
roles: UserRoleSchema,
scores: [UserScoreSchema]
});
var UserGroupSchema = new Schema({
groupId: {
type: Schema.Types.ObjectId,
unique: true,
sparse: true
},
joinDate: {
type: Date,
default: Date.now
},
receiveNotifications: {
type: Boolean,
default: true
},
isAdmin: {
type: Boolean,
default: false
},
isOwner: {
type: Boolean,
default: false
},
isModerator: {
type: Boolean,
default: false
},
updateDate: Date
});

If you are applying upsert on array of object then this will always create new document as it do not compare sub documents of an array and you have unique index on groupId so it is not allowing you to create new record with the same value. For it you should find that record and if exists, then update it else create new record.
Another best way is to use $addToSet.
Hope this helps.

The {unique: true} requirement on the groupId field means that no two documents in the collection may contain the same groupId, rather than what you intended, enforcing uniqueness of the groupIds within the document. You can do what you want by using the MongoDB $addToSet operator instead.

If you are trying to update existing group from groups array, $push is not the solution.
User.findAndUpdate({_id:userId,'groups.groupId': userUpdate.groupId},
{ $set: {'groups.$': userUpdate}},
function (err, obj){})
otherwise as other suggested $addToSet will add the element into set if it exists.
User.findByIdAndUpdate(userId,
{ $addToSet : { 'groups': userUpdate} },
function (err, obj){})

Related

Unable to get _id which is not ObjectID with mongoose

I can't access to _id of any mongoose.model object. I know there are a lot of similar answers but none of it solved my problem. I am using mongoose and TypeScript.
I've existing collection which contains data with existing Mixed _id:
{
_id: 10,
name: "someString",
email: "someString",
}
...
I've Schema and interface:
const UserModel: Schema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
});
export interface IUser extends Document {
name: string;
email: string;
}
export default mongoose.model<IUser>('User', UserModel);
and I try to select some user:
UserModel.findOne({email:data.email}).then((user)=>{
console.log(user);
// I get everything without _id
// { name: "someString", email: "someString" }
console.log(user.id);
// null
});
Another attempt
I've also tried to set _id to false in options:
const UserModel: Schema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
}, { _id: false });
and I tried to select some user:
UserModel.findOne({email:data.email}).then((user)=>{
console.log(user);
// I get everything with _id
// { _id: 10, name: "someString", email: "someString" }
console.log(user.id, user._id);
// but it is not accessible
// null, undefined
});
Note
If I create document record through mongoose it creates _id as ObjectId() and it is selectable.
Mongoose expects an ObjectID for _id
Add your custom type to the schema
const UserModel: Schema = new Schema({
_id: { type: mongoose.Schema.Types.Mixed, required: true },
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
});
The _id index can't be modified so don't worry about adding index options.
It's usually best to leave Mongo to use it's ObjectID.

How do you save certain items for specific users in mongodb?

I'm working on a job tracker app.
User creates an account, saving the user in a mongodb collection.
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
const JobSchema = new mongoose.Schema({
position: {
type: String,
required: true
},
company: {
type: String,
required: true
},
status: {
type: String,
default: "applied"
},
date: {
type: Date,
default: Date.now
}
});
When a user adds a job, how would you store (.post) and retrieve (.get) that data to correspond to that specific user only?
Is there a way to save the users "_id" to the jobs added, and searchById to get the jobs?
It depends what exactly you want to achieve meaning what type of relationships your models will have. Lets say your users will have multiple jobs the best approach would be to store an array of ObjectIds. The refoption tells mongoose which collections to search during population of the array
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
jobs: [{type:Schema.Types.ObjecId,ref: 'Job'}]
});
and then when you query the database you chain populate('jobs') after the query.
You can read more on the subject here
For example,
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', UserSchema);
async function updateUser() {
let user = await User.findOne({"name": "a-unique-user-name-in-the-db"})
let userId = user._id
let newEmail = "asdf#asdf.com"
let updated = await User.updateOne(
{ "_id": userId },
{
$set: {
"email": newEmail,
}
}
)
if (updated) {
console.log("updated")
}
}
updateUser();

How to change boolean value within an object of a document sub-array in Mongoose?

I have a rooms model. Within it is an array of User's which has its own model.
Each user has a bunch of different attributes, some of them being boolean. Knowing the ID of the specific room and the specific user, I am attempting to change the boolean value within a specific User element within the sub array like this:
Room.findOne({_id: roomId, "users" : user}, { "$set" : { mutedAudio : false}})
.then(doc => {
console.log("Unmuted audio");
res.json(doc)
io.in(roomId).emit('userchange');
})
.catch(err => {
console.log(err);
})
(I'm using a user model instead of a user ID for seeking the user within the sub array. Could not get ID to work but can fetch object by comparing it to itself entirely.)
I get the error:
MongoError: Unsupported projection option: $set: { mutedAudio: true }
Anyone know the answer to this?
Thank you.
EDIT:
const RoomSchema = new Schema({
owner: {
id: {
type: String
},
username: {
type: String
}
},
roomname: {
type: String,
required: true
},
category: {
type: String,
required: true
},
password: {
type: String,
required: false
},
users: [UserSchema],
messages: [{
username: {
type: String
},
message: {
type: String
},
time: {
type: String
}
}],
date: {
type: Date,
default: Date.now
}
});
const UserSchema = new Schema({
id: {
type: String
},
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
avatar: {
type: String
},
date: {
type: Date,
default: Date.now
},
micEnabled: {
type: Boolean,
default: false
},
mutedAudio: {
type: Boolean,
default: true
}
});
Model.findOne() takes 4 parameters, the second being "optional fields to return", that's why you're getting the error, mongoose is trying to select fields to return according to $set: { mutedAudio: true } which is being passed as a second parameter (therefore considered to be a projection option).
Use Model.findOneAndUpdate() which takes an update object as a second parameter, along with the positional operator $.
Room.findOneAndUpdate(
{ "_id": roomId, "users._id": userID },{ "$set": { "users.$.mutedAudio": false } } )
.then(doc => {
console.log("Unmuted audio");
res.json(doc)
io.in(roomId).emit('userchange');
})
.catch(err => {
console.log(err);
})
Original answer by #Neil Lunn in Mongoose find/update subdocument

Delete only those, which have no Entry in first table corresponding to the second table

var productSchema = Schema({
product_code: String,
name: {
type: String,
required: true
},
description: String,
category:{
type: String,
ref: 'Product_Category'
},
umo: String,
threshold: {
type:Number,
default: 0
},
image: String,
isactive: {
type: Boolean,
default: true
}
});
var product_categorySchema = Schema({
isactive: {
type: Boolean,
default: true
},
name: {
type: String,
required: true
},
description: String
});
I have these two schema I am deleting from category, but if I have data corresponding to that category in product table then that category should not be deleted. Can anyone help?
it should look like something like this :
// Function which delete the category behind the given _id
async function deleteCategory(idCategory) {
// check if there is a product related to the category
const ret = await product_schema.findOne({
category: idCategory,
});
// if there is, return an error
if (ret) throw new Error('Cannot delete the category');
// else do delete the category
return product_category_schema.remove({
_id: idCategory,
});
}
Also you have to know that :
category:{
type: String,
ref: 'Product_Category'
},
is not the right way to setup a reference; it should be an ObjectId not a String
const {
Schema,
} = mongoose;
category:{
type: Schema.Types.ObjectId,
ref: 'Product_Category'
},
Firstly, please update "type" property of "category" field in product schema like this:
category:{
type: Schema.Types.ObjectId,
ref: 'Category' // model name
}`
and declare model like this :
var Product = mongoose.model('Product', productSchema );
then use "distinct" query and "$nin" query-operator to delete category which are not referenced by product schema like this :
Product.find().distinct('category').then((data)=>{
Category.deleteMany({_id: {$nin: data}}).then(del => {
console.log("deleted",del)
})
})

Mongoose populate returns empty array but returns data without populate

I am trying to fetch appointments and populate them with user details and doctor details. I have a schema like this
let appointmentSchema = new Schema({
reason: String,
start: DateType,
end: DateType,
appointmentStamp: Number,
userId: {
type: ObjectId,
ref: 'User'
},
doctorId: {
type: ObjectId,
ref: 'Doctor'
},
approved: Boolean,
held: Boolean,
timeOfAcceptance: Number,
createdOn: { type: Number, default: (new Date().getTime()) },
updatedAt: Mixed
}, {runSettersOnQuery: true})
And on running
Appointment.find({userId: authId}).populate('userId doctorId').exec()
I get an empty array
but
Appointment.find({userId: authId}).exec()
return all the appointments for that user with the userId supplied in find
what am I missing out
Here is a sample appointment
{
"_id": "5a0d424079191657fca9e853",
"reason": "Say Hello",
"approved": false,
"held": false,
"doctor": "5a0add9b577d7916187cd9f2",
"patient": "59f5df20a3071554c5f843e1",
"appointmentStamp": 1510927380000,
"__v": 0,
"createdOn": 1510817910134
}
I tried this and it worked, don't know why
patient: {
type: String,
ref: 'patient'
},
doctor: {
type: String,
ref: 'doctor'
}
instead of ObjectId I used type of String
then the query be like
Appointment.find({patient: authId, canceled: false})
.populate('patient doctor').exec()

Categories

Resources