Mongoose/keystonejs: unique field except if field is empty - node.js

I have an email field, where I want unique emails. I also want to have the possiblity of an empty field. I don't want it to check uniqueness if the email is empty/null/not present.
Here is my List schema defined in Keystone:
Book.add({
number: { type: Types.Number },
email: { type: Types.Email, initial: true, index: true, unique: true },
token: { type: Types.Text },
title: { type: Types.Text, initial: true },
author: { type: Types.Text, initial: true },
name: { type: Types.Text, initial: true },
dedication: { type: Types.Textarea, initial: true },
image: { type: Types.CloudinaryImage }
});
Keystone exposes the mongoose schema, so I can access that. I could add a custom validation which makes a query except if field empty, but I'm hoping for something more elegant.
I already have plenty of data, I don't know if that makes the indexing stuff more complex.

I assume from the fact that you're asking this question that unique: true is not doing what you want when the email field is left empty.
You could try setting sparse: true as shown here, if keystone simply passes that straight to the schema you should be fine.
If not, the best way, as you mention, is probably just to add a simple pre-save hook to do this for you, as there are currently no options in keystone to allow you to do this.
Hope that helps (and let me know if sparse: true works)!

Use Books.schema.index({email: 1}, {sparse: true}) after you add the field and remove the index: true from the field. This calls Mongoose directly.
Don't forget to remove the old index from your collection (or simply drop the table).
What I don't understand is why it wouldn't just work, because the options seem to be passed to Mongoose directly: https://github.com/keystonejs/keystone/blob/94b34fe2239a0b571bf1421d45493d32896502a9/fields/types/Type.js#L242
Perhaps it already worked and you had the old unique index on there?

Related

Mongoose validator on ObjectID not triggered

I have a Pet schema defined as follows:
export default {
animal: {
type: Schema.Types.ObjectId,
validate: {
validator(v) {
if (
(!this.animal && !this.crossbreed_animal) ||
(this.animal && this.crossbreed_animal)
)
return false;
return true;
},
message:
"You must provide one of 'animal' or 'crossbreed_animal' ObjectId",
},
},
crossbreed_animal: { type: Schema.Types.ObjectId, required: false },
user: { type: Schema.Types.ObjectId, required: true },
name: { type: String, required: true },
birth_date: { type: Date, required: true },
weight: { type: Number, required: false },
...
}
The issue is the validator not being triggered at all. (I ensured this by putting console.log() in various places in the validator)
However, it works flawlessly when the type field is not Schema.Types.ObjectId. I tried with the weight and name fields by copy/pasting the exact same validator code and it works as expected.
I google'd this issue but it seems like no-one has the same error.
What's the problem here?
Node version: v14.15.0,
Mongoose version: v.5.10.9
I would advise you to not put a validator that performs a validation of multiple fields on some fields like animal.
The best way to handle this is to define a pre('save') hook that will perform complex validation of the whole object. Though this does not actually answer why it does not work with ObjectID I would also advise you to submit an issue in the Mongoose repository if this is crucial for you.

Filter documents using multiple fields

I have a collection named Project. Below is the schema description for Project collection :
const projectSchema = new mongoose.Schema(
{
projectName: {
type: String,
trim: true
},
licenceType: {
type: String,
trim: true,
lowercase: true
},
location: {
type: String,
lowercase: true,
trim: true
},
description: {
type: String,
trim: true
},
projectType: {
type: String,
default: 'public'
},
status: {
type: String,
default: 'open'
},
budget: {
type: Number
},
duration: {
type: Number
},
},
{
timestamps: true
}
);
I want to fetch documents using these fields as filters. The fields are :
licenceType
location
date (timestamp is made true in schema for this purpose)
I can use these three fields in any combination to fetch documents. There is a possibility that no filter is applied at all in which case its a simple fetching of all the documents in the collection.
I know I can dynamically build query using if--else if--else but I wanted to know is there any other more efficient way of handling such queries rather than using if--else blocks. If there would have been five or more fields for filtering purpose, there would be so many combinations to check using if--else block.
Appreciate any kind of help!!Thank You.
So, I assume there's some external trigger which actually modifies the filter matrix (eg. a request from some UI). Mongoose allows you to specify filter up-front in a form of an object, take a look below:
const query = Project.find({
licenceType: 'sometype',
location: 'somelocation'
});
Clearly you can see this...
{
licenceType: 'sometype',
location: 'somelocation'
}
...is an object. So, I think you could re-build the filtering object each time filters change (create an empty object let myFilters = {} and extend it with your filters: myFilters['licenceType'] = 'sometype') and pass myFilters to find function.

Mongoose - How to prevent mongoose from creating _id on model instantiation

I want MongoDB itself to add an _id upon insertion so I can track the insertion time using the ObjectID but when I do new MyModel(...), moongose will add the id field.
How do I prevent this so the db itself adds the id?
Alternatively how do I create a field which will be set to the INSERTION time by the db?
Edit: I see that this is not technically possible with mongoose, so would it be possible to add a field that is set by MongoDB when the insertion is done?
My model (if relevant):
{
timestamp: {
type: Date,
required: true
},
signaler: {
type: String,
required: true,
trim: true
},
source: {
type: String,
required: false,
trim: true
},
category: {
type: String,
required: false,
trim: true
},
key: {
type: String,
required: true,
trim: true
},
level: {
type: String,
required: false,
trim: true,
uppercase: true,
enum: ['ALARM', 'WARNING', 'NORMAL']
},
payload: {
type: Schema.Types.Mixed,
required: true
}
}
It's an interesting use case you have. Mongoose really does create that _id on when you call a Model constructor!
I see three paths forward:
Don't use mongoose. The lower level mongo driver doesn't create _ids util you insert into a collection.
Pass around a plain javascript object until you are ready to save
it, then use the Model.create method.
Finally, you can use the pre save middleware to update the _id, by manually generating a new one (with mongoose.Types.ObjectId()) that will have more accurate time info.
If you want to introduce a createdAt field that is updated when the document is inserted, then you are also going handle the pre save middleware. That's the way this popular plugin does it: https://github.com/drudge/mongoose-timestamp/blob/master/index.js

Mongoose does not create text index

I have the following schema:
entrySchema = new mongoose.Schema({
size: { type: Number },
title: {type: String, trim: true },
content: { type: String, trim: true },
tags: { type: [String], trim: true, index: true },
author: { type: String, trim: true, index: true }
});
entrySchema.index({ title: "text", content: "text" });
module.exports = mongoose.model('Entry', entrySchema);
The problem is that mongoose does not create the text indexes. The indexes for tags and author are created correctly, though.
Am I using the index() function in a wrong way?
I don't get any errors in the mongod session. It logs successful index creation for the non-text indexes, but it seems as if mongoose never calls ensureIndex for the text indexes.
After debugging as described in Mongoose Not Creating Indexes (thanks to #JohnyHK for the link) I saw that the actual problem was not the text index.
I was using the mongoose-auto-increment plugin and that resulted in errors indexing the _id field.
The solution was to have autoIncrement not use the _id field but a separate field like this:
entrySchema.plugin autoIncrement.plugin, {
model: 'Entry'
startAt: 1000
field: 'shortId'
}
I just did not thing about that because indexing worked fine without the text index. There seems to be some kind of incompatibility with the plugin and text indexes.

How to ensure uniqueness in a collection of embedded document

I have a User (document) in which I maintain a collection of Friends (embedded document). I've been trying to make sure that for a given user, all its friends are unique (email must be unique). However that constraint isn't enforce and I can't understand why. Anybody could please help me with that?
I am using Mongo 2.2 and Mongoose.
var FriendSchema = new mongoose.Schema({
email : { type: String, required: true, lowercase: true, trim: true }
, name : { type: String, trim: true }
});
var UserSchema = new mongoose.Schema({
email : { type: String, required: true, index: { unique: true }, lowercase: true, trim: true }
, friends : [FriendSchema]
});
UserSchema.index({ 'friend.email': 1 }, { unique: true });
Unique indexes means that no other documents are inserted with the same value specified in the index, this doesn't apply to embedded documents. If you tried to add foo#example.com to users A and B, it should fail due to the unique index.
You would have to enforce this in Node.JS rather than trying to do it with an unique index.

Resources