I have the following schematics:
let CollaborationSchema = new Schema({
role:{
type: String,
required: [true, "El rol es requerido"],
default: "USER"
},
company: {
type: Schema.Types.ObjectId,
ref: 'Company',
required: [true, "La compania es requerida"]
},
area:{
type: Schema.Types.ObjectId,
ref: 'Subsudiary'
},
state:{
type: Boolean,
default: false
}
})
let userSchema = new Schema({
name:{
type: String,
unique: false,
required: [true, 'El nombre es requerido']
},
collaborations: {
type: [CollaborationSchema],
required: [true, "La colaboracion es requerida"]
}
})
I want him to User
have an array of companies in which he collaborates, therefore, I want to restrict the collaborations (within the array) to be unique.
For example:
db.users.insert({name:"Jose", [
{role:"USER", company: 1, state: true},
{role:"USER", company: 2, state: true},
{role:"USER", company: 3, state: false}
]}) // ==> TRUE
db.users.insert({name:"Jose", [
{role:"USER", company: 1, state: true},
{role:"USER", company: 2, state: true},
{role:"USER", company: 1, state: false}
]}) // ==> FALSE
Try creating indexes with mongoose as follows:
userSchema.index({ email: 1, 'collaborations.company': 1 },{ unique: true });
But it did not work, try to define the indexes in the schema CollaborationSchema
in the following way:
CollaborationSchema.index({ company: 1 },{ unique: true });
But this solves it half way, since mongo does not allow me another user to collaborate in the same company as "Jose".
ISSUE
You want to create a non-repeating list of a specific field in a MongoDB document .
Suppose we have the list of collaborations as follows:
As can be seen, the first and third elements differ only by the field
state
.The thing to keep in mind is what value can be repeated and what value cannot.
If a user can only collaborate on 1 company, then the unique field for each document in the list must be
company
.In your question it is clear that this is the situation, you can only add collaborations in different companies to the list. A user cannot be a collaborator more than once in the same company (regardless of role or status or any other field).
This poses a problem for us when updating the data, since although MongoDB provides us with the operator
$addToSet
to add values to a list without repetition, as we have already seen, the objects compared in this way may be different in some other field and therefore both are cataloged as different.SOLUTION
One solution is to filter the collaboration documents based on the value of
company
, this way you won't be able to add duplicate items.However we have 2 scenarios:
insert
update
Creating documents with
insert
in MongoDB orsave
in MongooseAssuming I'm going to create a document in MongoDB, we have no way to ensure during creation that the list of collaborations doesn't contain duplicate items. So we need to purge the data, before inserting it, in some way.
filter
For this we will rely on the Javascript method .For example:
If we want to do the same thing with Mongoose, it's easy since the code is basically the same, except we'll use
save()
our model's method:Mongoose: Using a middleware function in a type hook
pre-save
If we want to automate the task of filtering the documents by the field
company
, we can delegate this task to a pre - type middleware that will always be executed before saving a document created from our model.Our scheme would look like this:
In this way, every time a document is created using the user model, when saving it, our middleware function is executed , which is responsible for eliminating duplicate records on the field
company
.Updating documents with
update
MongoDB and/or MongooseWe are now faced with the situation of updating an existing document. For this we have 2 scenarios:
In both cases it is an update on a document in the users collection.
Update an existing item
Attacking the first case is very simple using a filter
Array
( arrayFilters ) that work the same in both MongoDB and Mongoose.Suppose we are going to modify the field
rol
of the list item whose fieldcompany
is 2, in the document belonging to the user identified by the name 'JOSE' (although it is more common to use the_id
document field).So, our filter
Array
can be written as:I am indicating with this that I am going to update the element of the list, whose field
company
is equal to 2.Then in the update statement I use this filter as follows:
If everything is correct we will have the message:
And the document could look like this:
We have successfully changed the user role for collaboration in the company identified by the number 2.
In Mongoose the code is the same, except we apply it to the user model:
Insert a new item in the list
To insert a new element in the list we are going to resort to the operator
$nin
and of the$push
MongoDB operator.Let's suppose that now our user is a collaborator in a company identified by the number 3. Then we could use the method
updateOne()
in the following way.In this case, I am updating a document from the users collection whose field
name
is 'JOSE' and that does not have any element in the list of collaborations with the fieldcompany
equal to the fieldcompany
of the collaboration that I want to insert.If said filter does not return any document, it means that a collaboration already exists in the company indicated by the field
company
, therefore the operation of adding the collaboration to the list is not carried out.On the contrary, if there is a document in the users collection that in its list of collaborations are all different from the one I want to insert, then the update will be performed.
If all goes well the output will look similar to the following:
We have used the parameter
upsert
explicitly to tell MongoDB that if the document does not exist (based on the filter used) it should not create a new document. Although this parameter is thefalse
default, it is always a good practice to show it in the code so that the intention is understood when performing the update operation.Again in the case of Mongoose the code is the same except for the fact that we are going to use the
updateOne
model method:This would be a way to attack the problem posed in the question.