Add/Remove users to an array of ObjectId Fails - node.js

Thank you for taking out time to read this.
I have two models positions and users. I'm trying to add 'users' to the Array of 'Recruiters' as seen below in positions Model. When I make the put request, Everything goes well but my amended array which includes the new userids fail to save and give the following error.
'Cast to [ObjectId] failed for value "[3]" at path "recruiters"'
PositionsModel.js
const positionsSchema = new Schema(
{
title: {
type: String,
required: [true, 'Position title is required'],
},
description: {
type: String
},
recruiters: [{
type: Schema.Types.ObjectId,
ref: "users"
}]
},
{ timestamps: true }
);
usersModel.js
const usersSchema = new Schema(
{
name: {
type: String,
required: [true, "Name must be provided"],
},
email: {
type: String
},
password: {
type: String,
}
},
{ timestamps: true }
);
Controller.js (Problem Here)
I'm making a put request to add recruiter to the Array in Positions Model and sending two parameters. Id (this is the position id) and Recruiter (this is the userId)
exports.addRemove = async (req, res, next) => {
try {
const {id, recruiter} = req.params;
//Get Current Position Details
const position = await positionsModel.findById(id)
// Update Position
const newList = position.recruiters.push(recruiter) //this works, It adds the id to array
const newData = {
recruiters: newList
}
//At this point if you console log position.recruiters. You will see the newly added item in the array
const uptPosition = await positionsModel
.findByIdAndUpdate(id, newData, {
new: true,
runValidators: true,
})
.exec(); // this fails with error
if(!uptPosition) {
return res.status(400).json("Position failed to update");
}
//Success Response
return res.status(200).json('Position Updated');
} catch (err) {
console.log({ Error: err.message });
return;
}
Current List of Recruiter Id's in the array
The Current Recruiter Array already has two userIds. The third one gets added successfully to the newList variable, but it doesn't get saved in the database. You can see the error below as it points to the third element that was just added in the controller
'Cast to [ObjectId] failed for value "[3]" at path "recruiters"'

The push() method adds one or more elements to the end of an array and returns the new length of the array.
You did:
const newList = position.recruiters.push(recruiter);
Then newList will be the new length of recruiters array(in your case is 3). You can fix this by changing your code to:
position.recruiters.push(recruiter);
position.markModified('recruiters'); // mark recruiters as having pending change
position.save();

Related

Ignore null or empty values when saving a mongoose array schema type

I have the following schema:
const userSchema = new mongoose.Schema({
email: [{
type: String,
trim: true,
}]
})
When saving a new user,
const user = new User({
email: ["example#example.com", ""]
//or email: ["example#example.com", null]
})
try{
await user.save()
} catch (e) {
console.log(e)
}
This will save both those values (including empty string and null respectively).
Is there a way to save only the proper email value while discarding the empty or null value.
Instead of this:
"email" : [
"example#example.com",
""
],
store only the proper email:
"email" : [
"example#example.com",
],
Currently for other schema fields I am using set. For example, in the user schema above
url: {
type: String,
set: deleteEmpty
}
const deleteEmpty = (v) => {
if(!v) {
return undefined
}
return v
}
This will of course not save the url field at all if the value is empty or null.
Using this method on the email field above however will generate a null value.
Is there a way to store only the proper email value (i.e. "example#example.com" in this case while ignoring the null or empty value.)?
👨‍🏫 I think you can make it something like this code below 👇 with your userSchema:
userSchema.pre('save', function(next) {
this.email = this.email.filter(email => email);
next();
})
The code above ☝️ will igrone all empty or null value in an array. You can try it.
Or the Second Options, you can add required on your email field in your userSchema. It's well looks like this code below: 👇
const userSchema = new mongoose.Schema({
email: [{
type: String,
trim: true,
required: true // add some required
}],
});
💡 The code above ☝️, will give you an error if you passing an empty string on your array.
I hope it's can help you 🙏.
You can do the following to achieve what you want.
var arr = ['example#example.com', '']; // variable to keep the an array containing values
var i;
for (i = 0; i < arr.length; i++) {
if (arr[i] == null || arr[i] == '') {
arr.slice(i); // remove null or '' value
}
}
console.log('normalized array: ', arr);
// schema code
const user = new User({
email: arr
})
try{
await user.save()
} catch (e) {
console.log(e)
}
Good luck, I hope I answered your question.
If anyone has one or more fields that takes array value and wants to check for each field, I recommend using a middleware on the pre save hook.
supplierSchema.pre('save', normalizeArray)
const normalizeArrray = function(next) {
//take the list of object feilds and for each field check if it is array
Object.keys(this.toObject()).forEach((field) => {
if(Array.isArray(this[field])) {
//removes null or empty values
this[field] = this[field].filter(field => field)
}
})
next()
}
This just builds on the answer already approved above.
Simply set the default value of the field you wish to ignore, if empty, to undefined. Also, set required to false.
Use the code below:
const userSchema = new mongoose.Schema({
email: [{
type: String,
trim: true,
required: false,
default: undefined
}]
})

Mongoose: How to ensure row should be unique only if value of property is equal to something?

I have a simple Mongoose schema that looks like this:
const fruitSchema = new mongoose.Schema({
name: {
type: String
required: true
},
type: {
type: String,
required: true
}
})
schema.index({ name: 1, type: 1 }, { unique: true })
const Fruit = mongoose.model('Fruit', fruitSchema)
Under normal circumstances, the table is unique on (name, type) so the user is able to store multiple types for each fruit. However, I want to allow the user to only store one type if the name of the Fruit is apple.
So basically, I want make the following check before a save is made:
if (newFruit.name === 'apple') {
if (Fruit.find({ name: 'apple' }).count >= 1) throw new Error()
}
One way of enforcing this is to execute the code above in a pre-save hook. But, I was just wondering if there would be an in-built way to specify this within the Mongoose schema itself?
Thanks for the help!
SOLUTION:
In addition to the solution kindly provided by #SuleymanSah below, I thought I'd post what I finally ended up using.
const fruitSchema = new mongoose.Schema({
fruit: {
type: String
required: true,
async validate(name) {
if (name === 'apple') {
let fruit
try {
fruit = await Fruit.findOne({ name })
} catch (e) {
console.error('[Fruit Model] An error occurred during validation.')
console.error(e)
throw e // rethrow error if findOne call fails since fruit will be null and this validation will pass with the next statement
}
if (fruit) throw new Error(`A fruit for ${name} already exists.`)
}
},
type: {
type: String,
required: true
}
})
schema.index({ fruit: 1, type: 1 }, { unique: true })
const Fruit = mongoose.model('Fruit', fruitSchema)
You can use custom validators like this:
const schema = new mongoose.Schema({
name: {
type: String,
required: true
},
type: {
type: String,
required: true,
validate: {
validator: async function() {
if (this.name === "apple") {
let doc = await this.constructor.findOne({ name: "apple" });
return Boolean(!doc);
}
},
message: props => "For apple only one type can be."
}
}
});
You can do it like this with express, for example when saving a new fruit:
const express = require("express");
const router = express.Router();
router.post("/save", (req, res) => {
const { name, type } = req.body; //this is coming from front-end
Fruit.findOne({ name }).then(fruit=> {
if (fruit) return res.status(400).json({ name: "This fruit already exist!" });
This will prevent any fruit with same name from saving to database
i think this is similar query
you need to add index and set it to unique
restrict to store duplicate values in mongodb

TypeError: expected string but received array postman

I was trying to send form data that has multiple fields with the same name, I'm getting back "TypeError: expected string but received array".
I think the problem is with postman, I want to have multiple participant fields, and those would be added to the should be added to the array.
final results of array
// this is from models/Battle
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create Schema
const BattleSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
date: {
type: Date,
default: Date.now
},
category: {
type: Number,
required: true // this will come from the selected category
},
winner: {
type: Number,
default: 0
},
status: {
type: Number,
default: 0 // 0 means the battle is closed, 1 means the battle is open for votes, the status will stay 0 until all participants dropped
},
participants: [
{
participant: {
type: Schema.Types.ObjectId,
required: true
}
}
]
});
module.exports = Battle = mongoose.model('battles', BattleSchema);
//this is from routes/api/battles
// #route POST api/battles
// #desc Create battle
// #access Private
router.post(
'/create-battle',
passport.authenticate('jwt', { session: false }),
(req, res) => {
const { errors, isValid } = validateBattleInput(req.body);
// Check Validation
if (!isValid) {
// If any errors, send 400 with errors object
return res.status(400).json(errors);
console.log(errors);
}
const newBattle = new Battle({
user: req.user.id,
category: req.body.category,
participant: req.body.participant
});
//save
newBattle.save().then(battle => {
// const participant = req.body.participant;
const participant = req.body.participant;
// add participants to array
battle.participants.push( participant );
console.log(typeof req.body.participant);
// get the inserted id
const battleId = battle._id;
res.json(battle);
});
}
);
// this is battle validation
const Validator = require('validator');
const isEmpty = require('./is-empty');
var bodyParser = require('body-parser');
module.exports = function validateBattleInput(data) {
let errors = {};
data.category = !isEmpty(data.category) ? data.category : '';
data.participant = !isEmpty(data.participant) ? data.participant : '';
if (Validator.isEmpty(data.category)) {
errors.category = 'Category field is required';
}
// if (Validator.isEmpty(data.challenger)) {
// errors.challenger = 'Challenger field is required';
// }
if (Validator.isEmpty(data.participant)) {
errors.participant = 'Participant field is required';
}
return {
errors,
isValid: isEmpty(errors)
};
};
TypeError: Expected string but received Array. ---throws an error in postman as well as in a terminal window. I suspect it could be the user schema definition mismatch
Please check your user model user schema eg
name: {
type: String,
required: true
}
it's receiving something else than expected.
try in your "body" tab, selecting "raw", and then to the right, select "JSON (application/json)" instead of "text".
I'm assuming your API endpoint uses JSON instead of a url-encoded form data, just because you are running an API using express and mongoose. but you should clarify that on the question if it isn't the case.
Write a proper JSON body, I mean, use double quotes for keys as in:
{"model": { "property": "value", "property2": 1}}
and try with the wrapping object {"model": <YOUR BODY HERE>} or without to see what works for you, as it's typical to wrap the object, but sometimes people don't use them. (seeing this in your code: req.body.participant makes me think you probably don't).
(PS: not related with the question, but personally prefer ARC or Insomnia for rest clients, as the interface for them is cleaner)
If you want data to be sent in participants array all the fields should be participants and not participant
try sending data through raw data and then selecting application/data for better formatting
When testing in postman - Just figured out Key value must match your validation function defined variables. It's better to be consistent across your development.

Node JS : how to fix validation failed cast to Array in mongoose

i'm trying to make an API like this
I tried to add a controller to the array, but I got a problem when I executed an error.
error like this
I have been looking for a solution from google and stackoverflow for last post but I got confused with that.
I am very grateful for the help
this is my code from controller
getCommunity: async (req, res, next) => {
const { playerId } = req.params;
const newCommunity = new Community(req.body);
//Get Player
const player = Player.findById(playerId);
// Assign player as a part of community's player
newCommunity.communityPlayer = player;
// Save the community
await newCommunity.save();
// Add community to the player's array
player.PlayerCommunities.push(newCommunity);
// Save the playerCommunities
await player.save();
res.status(201).json(newCommunity)
}
this from my model player
const playerSchema = new Schema({
playerName: String,
playerImage: { type: String, data: Buffer },
playerNumberPhone: {
type: String,
validate: {
validator: function(v) {
return /\d{3}-\d{4}-\d{4}/.test(v);
},
message: props => `${props.value} is not a valid phone number!`
},
required: [true, 'User phone number required']
},
playerAddress: String,
PlayerCommunities: [{type: Schema.Types.ObjectId, ref: 'community'}]
});
this is my model community
const communitySchema = new Schema({
communityName: String,
communityImage: {type: String, data: Buffer},
communityPlayer: [{
type: Schema.Types.ObjectId,
ref: 'player'}]
}, { usePushEach: true });
and this for my end point actually i using it
http://localhost:3000/player/:playerId/community
the way you assign newCommunity play to the array is wrong that's why it screams the error. try like this
getCommunity: async (req, res, next) => {
const { playerId } = req.params;
const newCommunity = new Community(req.body);
//Get Player
const player = Player.findById(playerId);
// Assign player as a part of community's player
newCommunity.communityPlayer.push(player); // here change your code like this
// Save the community
await newCommunity.save();
// Add community to the player's array
player.PlayerCommunities.push(newCommunity);
// Save the playerCommunities
await player.save();
res.status(201).json(newCommunity)
}
In the community model communityPlayer property is an array.
But in the controller you are assigning a non-array value.
Change following line of your controller
newCommunity.communityPlayer = player;
To
newCommunity.communityPlayer.push(player);

Mongoose TypeError: User is not a constructor

I'm trying to add a subdocument to a parent schema with Mongoose and MongoDB however I'm being thrown the following error:
TypeError: User is not a constructor
This is based off Mongoose's documentation on subdocuments and I think everything is the same. How can I debug this further?
Router
// Add a destination to the DB
router.post('/add', function(req, res, next) {
let airport = req.body.destination
let month = req.body.month
let id = (req.user.id)
User.findById(id , function (err, User) {
if (err) return handleError(err)
function addToCart (airport, month, id) {
var user = new User ({
destinations: [(
airport = '',
month = ''
)]
})
dog.destinations[0].airport = airport
dog.destinations[0].month = month
dog.save(callback)
res.status(200).send('added')
}
addToCart()
})
console.log(airport)
})
Schema
var destinationSchema = new Schema({
airport: String,
month: String
})
// Define the scheme
var User = new Schema ({
firstName: {
type: String,
index: true
},
lastName: {
type: String,
index: true
},
email: {
type: String,
index: true
},
homeAirport: {
type: String,
index: true
},
destinations: [destinationSchema]
})
User.plugin(passportLocalMongoose)
module.exports = mongoose.model('User', User)
JavaScript is case sensitive about the variable names. You have User model and the User result with the same name.
Your code will work with the following change :
User.findById(id , function (err, user) {
/* ^ use small `u` */
if (err) return handleError(err)
/* rest of your code */
Also keep in mind that further in your code you are declaring another variable named user. You will need to change that to something different.

Resources