How can I use partialFilterExpression on a mongoose model - mongodb

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.

Related

Populating multiple fields with a pre "find" middleware

I am trying to specify the fields that should always be populated for the user document in a pre "find" middleware, like this:
userSchema.pre(/^find/, function (next) {
this.populate("followers following"); next();
});
Here is the user schema:
const userSchema = new mongoose.Schema<IUser>(
{
firstName: {
type: String,
required: [true, "You must provide your first name."],
},
lastName: {
type: String,
required: [true, "You must provide your last name."],
},
profilePic: {
type: String,
},
email: {
type: String,
required: [true, "You must provide an email."],
unique: true,
},
password: {
type: String,
required: [true, "You must provide a password."],
},
isVerified: {
type: Boolean,
default: false,
},
verificationToken: {
type: String,
},
role: {
type: String,
enum: ["user", "admin"],
default: "user",
},
followers: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
following: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
},
{
timestamps: true,
}
);
But when I send the request it's just stuck not sending any response.
It works just fine when I only populate one field, either 'followers' or 'following', but together it won't work.
I tried a bunch of different ways, but nothing seems to work.
If anyone can help I would be very thankful!
#1 Have you tried to seperate them ?
userSchema.pre('find', function (next) {
this.populate("followers").populate("following");
next();
});
#2 Or with an array of paths
userSchema.pre('find', function (next) {
this.populate(['followers', 'following']);
next();
});
As documentation states here and here:
The Document#populate() method does not support chaining. You need to
call populate() multiple times, or with an array of paths, to populate
multiple paths.
#3 Or to use deep-populate if you're populating across multiple levels as documented here:
I've got the working solution.
The issue is that if the populate() method in the middleware has more than one field, it calls the middleware for each field and it triggers infinite recursion, which makes the request hanging.
The workaround is a bit weird, but pretty straight forward. We have to add the _recursed option so the middleware knows to avoid populating recursively.
userSchema.pre(/^find/, function (next) {
if (this.options._recursed) {
return next();
}
this.populate({ path: "followers following", options: { _recursed: true } });
next();
});

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

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

How to give iDs to dynamic fields in React-Redux?

I created a simple dynamic fields in React-Redux with a plus button to add as many field as I want (hobbies) of an already existing form. I'm using mongodb as a database and so I have this error that tells me that my fields/data don't have iDs.
so how can I generate iDs for my data?
this below is my model with featherJs. as you can see this is how I added my hobbies array in the existing model called myService. I can see that my hobbies are created in mongo (using Robo 3T) which is great but i'm having difficulty reusing them (hobbies) in an other component in Redux. I'm not sure if I should give IDs to this fields or create a new service just for them. I never coded something in backend so I'm confused. what's the rule for this kind of situations.
Any other suggestions would be helpful.
warning in Redux: Each child in a list should have a unique "key" prop.
error in api : Cast to ObjectId failed for value at path "_id" for model "
const { Schema } = mongooseClient;
const myService = new Schema({
type: { type: String, enum: VALID_TYPES, required: true },
user: {
type: mongooseClient.Schema.Types.ObjectId,
ref: 'user',
required: true
},
comment: String,
hobbies: [{
type: mongooseClient.Schema.Types.ObjectId,
ref: 'hobbies',
default: [],
required: false }],
date: {
begin: { type: Date, default: Date.now },
current: { type: Date, default: Date.now },
end: { type: Date, required: true },
},
}, {
timestamps: true
});
return mongooseClient.model('myService', myService);
};

How to make Mongoose not save a field if not listed on the schema

For example if I send a post request saving new user and add an additional field "Name": "oscar" this field is saving to mongodb event if is not listed on the schema. I need to prevent this from saving if is not listed on the schema.
const User = new Schema(
{
userName: {
type: String,
required: schemaErrorRequired('login'),
unique: schemaErrorUnique('login'),
},
email: {
type: String,
required: schemaErrorRequired('email'),
unique: schemaErrorUnique('email'),
},
pass: {
type: String,
required: 'Save error',
}
);
Apparently this is not happening with mongoose 5.8
add options to schema { strict : false}

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.