Is there a way to conditionally set 'unique' in Mongoose Schema? - mongodb

I came across a problem while implementing user deletion functionality. A
Suppose I have a model:
const UserSchema = new mongoose.Schema(
{
email: { type: String, required: true, unique: true },
name: { type: String, required: true },
password: { type: String, required: true },
deleted: {type: Date, default: null}
},
{ timestamps: true }
);
This clearly states that the field email has to be unique. However, I would like to set it unique only in the set filtered for deleted != null.
In other words, I would like to filter out the deleted users' records before checking if it is unique or not.
Are there any best practices regarding this?
Or should I just create a field called del-email and null the email field to avoid over-complication and preserve the data?

You can try,
Partial index with unique constraint:
The partial unique index, you can specify the filter expression condition, if it matches then the unique index will take the role,
UserSchema.index(
{ email: 1 },
{ unique: true, partialFilterExpression: { deleted: { $eq: null } } }
);
Note:
As noted in the query coverage documentation for partial indexes:
To use the partial index, a query must contain the filter expression (or a modified filter expression that specifies a subset of the filter expression) as part of its query condition.
User.find({ email: "something#mail.com", deleted: null });

Related

How to filter documents using find method in mongoose based on the data from reference in documents?

I am working on e-commerce like app. I have orderItem Schema
const orderItemsSchema = mongoose.Schema(
{
order: {
type: mongoose.Schema.Types.ObjectId,
ref: 'OrderItems',
required: true,
},
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Products',
required: true,
},
quantity: {
type: Number,
default: 1,
},
subCost: {
type: Number,
required: true,
},
},
{
timestamps: true,
}
);
Where product schema has a field "owner" which is also a reference.
I am expecting to get orderItems based on owners of the products.
For Example: A owner want to check which products of him has been sold. So he will query orderItems to get his sold items.
I'm not an expert in mongoose, so maybe the syntax is not entirely correct:
// You get all products _id that owner currently sells
const yourOwnerObjectId = mongoose.Types.ObjectId(yourOwnerId); // Create the objectId from a string
const productsOwner = Products.find({owner: yourOwnerObjectId}).select({_id: 1})
// You get all orders that contains any of previous product _id
const orderWithProductsSold = OrderItems.find({_id: {$in: productsOwner}})
I'm not sure about what returns the first query regarding _id. Maybe you have to do some type of casting to ObjectId or whatever to perform the second query, but I think the idea is right.

Have unique mongodb index on different layers of tree

I have a tree-like document model like the image below. Is it possible to create a unique index for different layers? For example, in the below example, I have index field 1, then different index fields in objects of l2 array and l3 array. I am trying to create an index where index of all layers together should be unique. For example, if I have an index 1, I can't have the same index value throughout the child documents or any other documents. I tried searching a solution for it, but couldn't find any. Please help me with this issue. Thanks in advance.
I'm assuming you are using NodeJs and Mongoose since you did not specify that. You can get an ObjectId for every level by using different schemas in nested objects like the below example.
const level2Schema = new Schema({
unit: {
type: String,
required: true
},
price: {
type: Number,
required: true
}
});
const level1Schema = new Schema({
feildx: {
type: String,
required: true
},
anyNameArray: {
type: [level2Schema],
required: true
}
});
var MainSchema = new Schema(
{
field1: String,
field2: String,
anyNameArray: {
type: [level1Schema],
default: [],
required: true
},
},
{ timestamps: true }
);
This will create a unique ObjectId for every nested document.

How can I use partialFilterExpression on a mongoose model

I have created a mongoose model that has an email field. I want it to be unique if a value is provided by a user but I want it to be empty is a user has not provided any value. I have found a good mongodb reference here: https://docs.mongodb.com/manual/core/index-partial/#partial-index-with-unique-constraints that could work but I don't know how to make it work on mongoose
This is how the field looks like right now
email: {
type: String,
index: true,
unique: true
}
If I leave it the way it is, I cant create multiple documents with an empty/null email field
In the email path level, you can use only:
email: {
type: String
}
And in the schema level use:
SchemaName.index({ email: 1 }, {
unique: true,
partialFilterExpression: {
'email': { $exists: true, $gt: '' }
}
});
This way the unique constraint is applied only if email exists and is not an empty string
You can have something like :
email: {
type: String,
index: {
unique: true,
partialFilterExpression: { email: { $type: 'string' } },
},
default : null
}
but do read below link, before you actually implement it, as defaults seems to work only on new document inserts :-
Mongoose v5.6.9 Documentation
You can use sparse
email: {
type: String,
unique: true,
sparse: true
}
That way if you dont't send the email field at all mongo will not add automatically null value for the field. It will just skip it.

How to select an element from a sub-documents array with two conditions?

Consider the following Mongoose schema:
new mongoose.Schema({
attributes: [{
key: { type: String, required: true },
references: [{
value: { type: String, required: true },
reference: { type: mongoose.Schema.Types.ObjectId, required: true }
}]
}
});
A document that follows this schema would look like this:
{
attributes: [
{
key: 'age', references: [{ value: '35', reference: 298387adef... }]
},
{
key: 'name', references: [{
value: 'Joe', reference: 13564afde...,
value: 'Joey', reference: 545675cdab...,
}
...
]
}
I'd like to select attributes according to the following conditions:
- the key is name for example
- the attribute with key name has a least one reference with a value Joe.
Ideally, I'd like to AND-chain many of these conditions. For example, {'name': 'Joe'} and {'age': '35'}.
I can't seem to find a way of doing that Mongoose. I've tried the following Mongoose queries without any good results (it gives either false positives or false negatives):
// First query
query.where('attributes.key', attribute.key);
query.where('attributes.references.value', attribute.value);
// Second
query.and([{ 'attributes.key': attribute.key }, { 'attributes.$.references.value': attribute.value }]);
// Third
query.where('attributes', { 'key': attribute.key, 'references.value': { $in: [attribute.value] }});
So how do I do it?
You can use elemMatch to find docs that contain an attributes element that matches multiple terms:
query.elemMatch(attributes, { key: 'name', 'references.value': 'Joe' })
You can't chain multiple elemMatch calls together though, so if you want to AND multiples of these you'd need to explicitly build up a query object using $and and $elemMatch instead of chaining Query method calls.

Mongoose document schema and validation

I Have a schema like so:
class Schemas
constructor: ->
#mongoose = require 'mongoose'
#schema = #mongoose.Schema
#EmployeeSchema = new #schema
'firstname': { type: String, required: true },
'lastname': { type: String, required: true },
'email': { type: String, required: true, index: { unique: true }, validate: /\b[a-zA-Z0-9._%+-]+#[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b/ },
'departmentId': { type: #schema.ObjectId, required: true }
'enddate': String,
'active': { type: Boolean, default: true }
#EmployeeSchemaModel = #mongoose.model 'employees', #EmployeeSchema
#DepartmentSchema = new #schema
'name': { type: String, required: true, index: { unique: true } }
'employees' : [ #EmployeeSchema ]
#DepartmentSchemaModel = #mongoose.model 'departments', #DepartmentSchema
So that my employees live in an array of employee documents inside a department
I have several department documents that have a number of employee documents stored in the employees array.
I then added a new department but it contained no employees. If I then attempt to add another department without employees, Mongoose produces a Duplicate key error for the employee.email field which is a required field. The employee.email field is required and unique, and it needs to be.
Is there anyway round this?
If you enable Mongoose debug logging with the coffeescript equivalent of mongoose.set('debug', true); you can see what's going on:
DEBUG: Mongoose: employees.ensureIndex({ email: 1 }) { safe: true, background: true, unique: true }
DEBUG: Mongoose: departments.ensureIndex({ name: 1 }) { safe: true, background: true, unique: true }
DEBUG: Mongoose: departments.ensureIndex({ 'employees.email': 1 }) { safe: true, background: true, unique: true }
By embedding the full EmployeeSchema in the employees array of DepartmentSchema (rather than just an ObjectId reference to it), you end up creating unique indexes on both employees.email and department.employees.email.
So when you create a new department without any employees you are 'using up' the undefined email case in the department.employees.email index as far a uniqueness. So when you try and do that a second time that unique value is already taken and you get the Duplicate key error.
The best fix for this is probably to change DepartmentSchema.employees to an array of ObjectId references to employees instead of full objects. Then the index stays in the employees collection where it belongs and you're not duplicating data and creating opportunities for inconsistencies.
Check out these references:
http://docs.mongodb.org/manual/core/indexes/#sparse-indexes
mongoDB/mongoose: unique if not null (specifically JohnnyHK's answer)
The short of it is that since Mongo 1.8, you can define what is called a sparse index, which only kicks in the unique check if the value is not null.
In your case, you would want:
#EmployeeSchema = new #schema
'firstname': { type: String, required: true },
'lastname': { type: String, required: true },
'email': { type: String, required: true, index: { unique: true, sparse: true }, validate: /\b[a-zA-Z0-9._%+-]+#[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b/ },
'departmentId': { type: #schema.ObjectId, required: true }
'enddate': String,
'active': { type: Boolean, default: true }
Notice the sparse: true added to your index on EmployeeSchema's email attribute.
https://gist.github.com/juanpaco/5124144
It appears that you can't create a unique index on an individual field of a sub-document. Although the db.collection.ensureIndex function in the Mongo shell appears to let you do that, it tests the sub-document as a whole for its uniqueness and not the individual field.
You can create an index on an individual field of a sub-document, you just can't make it unique.