0

I am using Mongoose with Javascript (NodeJS) to read/write to MongoDB. I have a Document (Parent) that has a bunch of Subdocuments (Children) in it. Both my Document and Subdocuments have validation (required: true and a function that validates that the user puts text in the field) defined in their Model.

When attempting to push a new Subdocument into the database, Mongoose rejects my push because validation fails on the Document. This has perplexed me as I am not trying to create a new Document with Subdocument, I am simply trying to push a new Subdocument into an existing Document.

Here is my (example) Mongoose Model:

const mongoose = require('mongoose');

const requiredStringValidator = [
  (val) => {
    const testVal = val.trim();
    return testVal.length > 0;
  },
  // Custom error text
  'Please supply a value for {PATH}',
];
const childrenSchema = new mongoose.Schema({
  childId: {
    type: mongoose.Schema.Types.ObjectId,
  },
  firstName: {
    type: String,
    required: true,
    validate: requiredStringValidator,
  },
  lastName: {
    type: String,
    required: true,
    validate: requiredStringValidator,
  },
  birthday: {
    type: Date,
    required: true,
  },
});
const parentSchema = new mongoose.Schema(
  {
    parentId: {
      type: mongoose.Schema.Types.ObjectId,
    },
    firstName: {
      type: String,
      required: true,
      validate: requiredStringValidator,
    },
    lastName: {
      type: String,
      required: true,
      validate: requiredStringValidator,
    },
    children: [childrenSchema],
  },
  { collection: 'parentsjustdontunderstand' },
);
const mongooseModels = {
  Parent: mongoose.model('Parent', parentSchema),
  Children: mongoose.model('Children', childrenSchema),
};
module.exports = mongooseModels;

I can successfully push a new Child Subdocument into the Parent Document via the following MongoDB command:

db.parentsjustdontunderstand.update({
    firstName: 'Willard'
}, {
    $push: {
        children: {
    "firstName": "Will",
    "lastName": "Smith",
    "birthday": "9/25/1968"        }
    }
});

However, when I follow the Mongoose documentation Adding Subdocs to Arrays and try to add it via Mongoose, it fails.

For testing purposes, I am using Postman and performing a PUT request against an endpoint. The following is req.body:

{
    "firstName": "Will",
    "lastName": "Smith",
    "birthday": "9/25/1968"
}

My code is:

const { Parent } = require('parentsModel');
const parent = new Parent();
parent.children.push(req.body);
parent.save();

What I get back is:

ValidationError: Parent validation failed: firstName: Path `firstName` is required...`

and it lists all of the Parent Document's validation requirements.

I could use some help on what I am doing wrong. For the record, I have looked at this answer on Stackoverflow: Push items into mongo array via mongoose but most examples I see do not show or discuss validation in their Mongoose Models.

EDIT 1

Based on feedback from @j-f, I modified my code to below (moving the body out of req.body and just creating it in code for testing purposes. When I attempt to push the update the way recommended, the record gets inserted, however, I still get a validation error thrown to console:

const parent = await Parent.findOne({firstName: 'Willard'});
const child = {
  children: {
      "firstName": "Will",
      "lastName": "Smith",
      "birthday": "9/25/1968"
  }
}
parent.children.push(child);
parent.save();
ValidationError: Parent validation failed: children.12.firstName: Path `firstName` is required., children.12.lastName: Path `lastName` is required., children.12.birthday: Path `birthday` is required.
Jeremy M
  • 179
  • 2
  • 13
  • You are creating a `Parent` empty and trying to save into DB. The parent created has no any property required (as `firstName`), is an empty object, only with property `children` and that is the fail. – J.F. Nov 25 '20 at 22:06

2 Answers2

1

ANSWER

@J.F is correct and I am wrong.

This is incorrect:

const child = {
  children: {
      "firstName": "Will",
      "lastName": "Smith",
      "birthday": "9/25/1968"
  }
}

This is correct:

const child = {
    "firstName": "Will",
    "lastName": "Smith",
    "birthday": "9/25/1968"
}

The record gets inserted into the database and saved but since I was initiating this as a PUT request, I was not properly responding after a successful save with an HTTP 200 OK. Correct code below for the entire solution, however, keep in mind the res.status code is only necessary in this scenario because I was imitating the code via a PUT request.

Mongoose Model:

const mongoose = require('mongoose');

const requiredStringValidator = [
  (val) => {
    const testVal = val.trim();
    return testVal.length > 0;
  },
  // Custom error text
  'Please supply a value for {PATH}',
];
const childrenSchema = new mongoose.Schema({
  childId: {
    type: mongoose.Schema.Types.ObjectId,
  },
  firstName: {
    type: String,
    required: true,
    validate: requiredStringValidator,
  },
  lastName: {
    type: String,
    required: true,
    validate: requiredStringValidator,
  },
  birthday: {
    type: Date,
    required: true,
  },
});
const parentSchema = new mongoose.Schema(
  {
    parentId: {
      type: mongoose.Schema.Types.ObjectId,
    },
    firstName: {
      type: String,
      required: true,
      validate: requiredStringValidator,
    },
    lastName: {
      type: String,
      required: true,
      validate: requiredStringValidator,
    },
    children: [childrenSchema],
  },
  { collection: 'parentsjustdontunderstand' },
);
const mongooseModels = {
  Parent: mongoose.model('Parent', parentSchema),
  Children: mongoose.model('Children', childrenSchema),
};
module.exports = mongooseModels;

The following is req.body:

{
    "firstName": "Will",
    "lastName": "Smith",
    "birthday": "9/25/1968"
}

Code is:

const { Parent } = require('parentsModel');
const parent = await Parent.findOne({firstName: 'Willard'});
parent.children.push(req.body);
parent.save((err, doc) => {
  if (err) {
    res.status(500).json({
      message: 'Error finding active projects',
      error: err,
    });
  } else {
    res.status(200).json(doc);
  }
});
Jeremy M
  • 179
  • 2
  • 13
0

You can push the children into the parent using a mongo query because into the update, the first object is to find the document where do the push.

The syntaxis is like: update({query},{update},{options}). So you are looking for a document with firstName: 'Willard' and adding the children into it.

Here everything is ok, all fields exists, the parent exists into collection so there is no problem.

But using

const parent = new Parent();
parent.children.push(req.body);
parent.save();

Your parent object is empty (unless the constructor fill all fields, but this is not a good idea I think).

If you try this:

var parent = await model.findOne({firstName: 'Willard'})
parent.children.push(req.body);
parent.save();

Then should works.

In this case, the object parent is retrieved from collection, so it contains all necessary fields.

I'm going to edit to explain better why is not thethe two queries are not the same.

Basically the child object you are trying to save has not the same structure as db.collection.update one. Note that the object child you have create to insert into collection has only one property called children. It doesn't have the necessary properties like firstName...

I'm going to use pure JS to can see what is the console.log() output and to see the differences.

Your mongo query push an object like this (translated to js language):

var array = []
array.push(
    children = {
        "firstName": "Will",
        "lastName": "Smith",
        "birthday": "9/25/1968"
    }
)

console.log(array)

But you are creating the object in this way:

const child = {
  children: {
      "firstName": "Will",
      "lastName": "Smith",
      "birthday": "9/25/1968"
  }
}

console.log(child)

Do you see now the difference? One object is the children itself the other object has the property children with the necessary fields.

So let's combinate the two pieces of code:

const child = {
  children: {
      "firstName": "Will",
      "lastName": "Smith",
      "birthday": "9/25/1968"
  }
}

const children = {
        "firstName": "Will",
        "lastName": "Smith",
        "birthday": "9/25/1968"
    }

var array = [child,children]
console.log(array)

So, for your code, if you use:

parent.children.push(child.children);
parent.save();

should works. BUT, the best ways is not created the object children inside const child

Try using:

const child = {
  "firstName": "Will",
  "lastName": "Smith",
  "birthday": "9/25/1968"
}
parent.children.push(child);
parent.save();
J.F.
  • 13,927
  • 9
  • 27
  • 65
  • I modified my question above based on your feedback. The record will get inserted but I get an error kicked back to me in console about validation still failing. – Jeremy M Nov 25 '20 at 23:24
  • You are creating an object `child` into `children` object, but your schema is not in this way. The properties should be into `children`. Is not necessary create an object `child` inside. – J.F. Nov 25 '20 at 23:30
  • I’m structuring the ‘child’ object the same way it’s structured in the ‘db.collection.update’ command. I’ve tried structuring ‘child’ without the root ‘children’ object. The code sits and does nothing. No error, or timeout, just sits. – Jeremy M Nov 26 '20 at 02:18
  • You were correct, J.F. How I was initiating the code, I was not responding with a 200K which is why the code appeared to be 'stuck'. I supplied an answer with that information and also marked your answer as accepted/correct (because it was). THANK YOU for your help! – Jeremy M Nov 26 '20 at 14:39
  • You are welcome! It is good to know that the problem is solved – J.F. Nov 26 '20 at 16:55