Mongoose: Validation not being executed when saving - arrays

I've defined this Mongoose schema:
// validator function
var arrayWithAtLeastFiveElements = function (a) {
return (a !== undefined && a.length >= 5);
};
var orderSchema = new Schema({
user: {
type: Schema.ObjectId,
ref: User,
required: true
},
products: [{
type: Schema.ObjectId,
ref: Product,
required: true,
validate: [arrayWithAtLeastFiveElements, 'Order needs to have at least five products']
}]
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
}
});
When I try to save it, the validation is not executed if products is undefined, null or an empty array, and it saves the new order with an empty array of products in each case. The validations are only run when products is an array with at least one element. Any clue what's going on? It there a way to make the validation run in all cases? Also, what does require do in this case? I don't see any change in validations if I define products array as required or not...

Define it with:
products: {
type: [Schema.ObjectId],
required: true,
}

Related

update one element of array inside object and return immutable state - redux [duplicate]

In React's this.state I have a property called formErrors containing the following dynamic array of objects.
[
{fieldName: 'title', valid: false},
{fieldName: 'description', valid: true},
{fieldName: 'cityId', valid: false},
{fieldName: 'hostDescription', valid: false},
]
Let's say I would need to update state's object having the fieldName cityId to the valid value of true.
What's the easiest or most common way to solve this?
I'm OK to use any of the libraries immutability-helper, immutable-js etc or ES6. I've tried and googled this for over 4 hours, and still cannot wrap my head around it. Would be extremely grateful for some help.
You can use map to iterate the data and check for the fieldName, if fieldName is cityId then you need to change the value and return a new object otherwise just return the same object.
Write it like this:
var data = [
{fieldName: 'title', valid: false},
{fieldName: 'description', valid: true},
{fieldName: 'cityId', valid: false},
{fieldName: 'hostDescription', valid: false},
]
var newData = data.map(el => {
if(el.fieldName == 'cityId')
return Object.assign({}, el, {valid:true})
return el
});
this.setState({ data: newData });
Here is a sample example - ES6
The left is the code, and the right is the output
Here is the code below
const data = [
{ fieldName: 'title', valid: false },
{ fieldName: 'description', valid: true },
{ fieldName: 'cityId', valid: false }, // old data
{ fieldName: 'hostDescription', valid: false },
]
const newData = data.map(obj => {
if(obj.fieldName === 'cityId') // check if fieldName equals to cityId
return {
...obj,
valid: true,
description: 'You can also add more values here' // Example of data extra fields
}
return obj
});
const result = { data: newData };
console.log(result);
this.setState({ data: newData });
Hope this helps,
Happy Coding!
How about immutability-helper? Works very well. You're looking for the $merge command I think.
#FellowStranger: I have one (and only one) section of my redux state that is an array of objects. I use the index in the reducer to update the correct entry:
case EMIT_DATA_TYPE_SELECT_CHANGE:
return state.map( (sigmap, index) => {
if ( index !== action.payload.index ) {
return sigmap;
} else {
return update(sigmap, {$merge: {
data_type: action.payload.value
}})
}
})
Frankly, this is kind of greasy, and I intend to change that part of my state object, but it does work... It doesn't sound like you're using redux but the tactic should be similar.
Instead of storing your values in an array, I strongly suggest using an object instead so you can easily specify which element you want to update. In the example below the key is the fieldName but it can be any unique identifier:
var fields = {
title: {
valid: false
},
description: {
valid: true
}
}
then you can use immutability-helper's update function:
var newFields = update(fields, {title: {valid: {$set: true}}})

How to add value to array element withing collection using mongoose?

I have written the following mongoose function to create new document in mongodb
createdata: (body) => {
let sEntry = new SData(Object.assign({}, {
dataId: body.DataId
//,
//notes.message: body.message
}));
return sEntry.save();
}
Here sData schema includes notes array schema within it.
I am not able to add value to message within notes [] using notes.message: body.message
My schema definition is as follows:
var nSchema = new Schema({
_id: {type:ObjectId, auto: true },
message: String
});
var sSchema = new Schema({
_id: {type:ObjectId, auto: true },
dataId: { type:String, unique: true },
notes: [nSchema]
}
I also want to mention that for every dataId there can be multiple notes [] entries. However, SData can have only unique row entry for every dataId.
I want notes to be an array within SData collection. How it can be achieved without creating separate notes collection? How should i modify createdata to accommodate all the given requirements.
Use references for other collection mapping and use populate when fetching
Schema Design
var sSchema = new Schema({
_id: {type:ObjectId, auto: true },
dataId: { type:String, unique: true },
notes: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'nSchema',
}]
}
Adding Data
createdata: (body) => {
let sEntry = new SData({
dataId: body.DataId,
notes: [nSchemaIds]
});
return sEntry.save();
}

Mongo schema, array of string with unique values

I'm creating the schema for a mongo document and I can do everything except prevent duplicates in a non-object array.
I'm aware of the addToSet, but I'm referring to Mongo Schema.
I don't want to check on Update using $addToSet, rather I want this to be part of my schema validation.
Example below.
let sampleSchema = {
name: { type: 'String', unique: true },
tags: [{ type: 'String', unique: true }]
}
The above snippet prevents name from having duplicate values. It allows tags to be stored as a string array.
But.. I cannot limit the array to be unique strings.
{ name: 'fail scenario', tags: ['bad', 'bad', 'array']}
I'm able to insert this record which should be a fail scenario.
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const _ = require('underscore');
let sampleSchema = new mongoose.Schema({
name: {
type: 'String',
unique: true
},
tags: [{
type: 'String'
}]
})
sampleSchema.pre('save', function (next) {
this.tags = _.uniq(this.tags);
next();
});
const Sample = mongoose.model('sample', sampleSchema, 'samples');
router.post('/sample', function (req, res, next) {
const sample = new Sample(req.body);
sample.save()
.then((sample) => {
return res.send(sample);
})
.catch(err => {
return res.status(500).send(err.message);
})
});
I've come to the conclusion that this is impossible to do via Mongoose Schema.
JSON schema is done like so.
let schema = {
name: { type: 'string' }
tags: {
type: 'array',
items: { type: 'string', uniqueItems: true }
}
}
I'll validate with JSON schema before creating Mongo Document.
This method builds on Med's answer, handles references, and done completely in scheme validation.
let sampleSchema = new mongoose.Schema({
strings: [{type: 'String'}],
references: [{type: mongoose.Schema.Types.ObjectId, ref: 'Reference'],
});
sampleSchema.pre('save', function (next) {
let sample = this;
sample.strings = _.uniq(sample.strings, function(i) {return (i._id) ? i._id.toString() : i;});
sample.references = _.uniq(sample.references, function(i) {return (i._id) ? i._id.toString() : i;});
return next();
});
I'm a little late, but maybe this will help someone in the future.
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type: String,
},
reference: {
type: [mongoose.Schema.Types.ObjectId],
ref: 'SomeOtherSchema',
// Add a custom validator.
validate: {
// The actual validator function goes here.
// "arr" will be the value that's being validated (so an array of
// mongoose new ObjectId statements, in this case).
validator: arr => {
// Convert all of the items in the array "arr", to their string
// representations.
// Then, use those strings to create a Set (which only stores unique
// values).
const s = new Set(arr.map(String));
// Compare the Set and Array's sizes, to see if there were any
// duplicates. If they're not equal, there was a duplicate, and
// validation will fail.
return s.size === arr.length;
},
// Provide a more meaningful error message.
message: p => `The values provided for '${ p.path }', ` +
`[${ p.value }], contains duplicates.`,
}
},
});
The above commented code should be pretty self explanatory.
With the newer version(s) of MongoDB, you can use $addToSet to append to an array if and only if the new value is unique compared to the items of the array.
Here's the reference: https://www.mongodb.com/docs/manual/reference/operator/update/addToSet/
Here's an example:
const SampleSchema = new mongoose.Schema({
tags: [String]
});
const Sample = mongoose.model('Sample', SampleSchema);
// append to array only if value is unique
Sample.findByIdAndUpdate({_id: 1, {$addToSet: {tags: "New Tag"}}});
This will effectively update the tags if the "New Tag" is not already present in the tags array. Otherwise, no operation is done.

Mongoose enum Validation on String Arrays?

Is it possible to use enum validation on type: [String]?
Example:
var permitted = ['1','2','3'];
var exampleSchema = new Schema({
factors: {
type: [String],
enum: permitted,
required: "Please specify at least one factor."
}
});
I would have expected that factors would only be able to contain the values in permitted.
This is working fine for me (mongoose#4.1.8)
var schema = new mongoose.Schema({
factors: [{type: String, enum: ['1', '2', '3'], required: ...}]
...
})
Note I'm using an Array of Objects
As of mongoose version 5.0.6 and higher, the OP issue now works!
factors: {
type: [String],
enum: permitted,
required: "Please specify at least one factor."
}
Reference
https://github.com/Automattic/mongoose/issues/6204#issuecomment-374690551
TRY THIS
let inventory_type_enum = ["goods", "services"];
inventory_type: {
type: String,
enum: inventory_type_enum,
validate: {
// validator: (inventory_type) => !inventory_type.enum.includes(inventory_type),
validator: (inventory_type) => inventory_type_enum.includes(inventory_type),
message: languages('general_merchandise_model','inventory_type')
},
required : [true, languages('general_merchandise_model','inventory_type_required')],
},
Mongoose before version 4.0 didn't support validation on Schema static methods like .update, .findByIdAndUpdate, .findOneAndUpdate.
But it supports on instance method document.save().
So, either use document.save() for inbuilt initiating validation
or this { runValidators: true } with methods like .update, .findByIdAndUpdate, .findOneAndUpdate.
reference:
Mongoose .update() does not trigger validation checking
if you have enuns or you have object enuns
brand: {
type: String,
required: true,
enum: Object.values(TypeBrandEnum)
},
you can use something like this
{
factors: [
{
type: [String],
enum: ['1', '2', '3'],
},
],
}

mongoose update with push operations on array and set operation on object

I have this mongoose schema
var ContactSchema = module.exports = new mongoose.Schema({
name: {
type: String,
required: true
},
phone: {
type: Number,
required: true,
},
messages: [
{
title: {type: String, required: true},
msg: {type: String, required: true}
}],
address:{ city:String,
state:String
}
});
I have initially the collection set with name and phone field. I need to update the collection with new messages into messages array and new address into address object. the function must also need to handle any single operation, ie in some case i have only update to messages array or updates to both name and address. so how i can i do all operations in a single function.
var messages= {
title: req.body.title,
msg: req.body.msg
}
Model.findOneAndUpdate({'_id': req.body.id,},{$push: {messages:message}},{upsert: true}, function (err, data) {
if (err) {
return res.status(500).send(err);
}
if (!data) {
return res.status(404).end();
}
return res.status(200).send(data);
});
You could try use both the $set and $push operators in your update object. Suppose, for example, you want to update both name and address fields in one single operation, use the $set on the name field and a $push operation to the address array:
var messages= {
title: req.body.title,
msg: req.body.msg
},
query = {'_id': req.body.id},
update = {
$set: {name: req.body.name},
$push: {messages: message}
},
options = {upsert: true};
Model.findOneAndUpdate(query, update, options, function (err, data) {
if (err) {
return res.status(500).send(err);
}
if (!data) {
return res.status(404).end();
}
return res.status(200).send(data);
});

Resources